From d39348d7bd1dbe13a2954835466c2242acf46014 Mon Sep 17 00:00:00 2001 From: ianmarshall Date: Sun, 9 Aug 2020 11:49:04 -0400 Subject: [PATCH 1/8] Initial changes to support multiple versions for code system. --- .../context/support/IValidationSupport.java | 31 +++++ .../ca/uhn/fhir/jpa/api/config/DaoConfig.java | 35 +++++ .../api/dao/IFhirResourceDaoCodeSystem.java | 5 + .../jpa/dao/FhirResourceDaoValueSetDstu2.java | 12 ++ .../dao/data/ITermCodeSystemVersionDao.java | 10 ++ .../dstu3/FhirResourceDaoCodeSystemDstu3.java | 22 +++- .../dao/r4/FhirResourceDaoCodeSystemR4.java | 22 +++- .../jpa/dao/r4/FhirResourceDaoValueSetR4.java | 18 ++- .../dao/r5/FhirResourceDaoCodeSystemR5.java | 22 +++- .../jpa/entity/TermCodeSystemVersion.java | 8 +- ...aseJpaResourceProviderCodeSystemDstu3.java | 3 +- .../BaseJpaResourceProviderCodeSystemR4.java | 3 +- .../BaseJpaResourceProviderCodeSystemR5.java | 3 +- .../fhir/jpa/term/BaseTermReadSvcImpl.java | 122 +++++++++++------- .../term/TermCodeSystemStorageSvcImpl.java | 64 +++++---- .../uhn/fhir/jpa/term/TermLoaderSvcImpl.java | 4 + .../uhn/fhir/jpa/term/TermReadSvcDstu3.java | 7 +- .../ca/uhn/fhir/jpa/term/TermReadSvcR4.java | 7 +- .../uhn/fhir/jpa/term/api/ITermReadSvc.java | 4 + .../term/loinc/LoincUploadPropertiesEnum.java | 3 + .../jpa/term/loinc/loincupload.properties | 4 + .../FhirResourceDaoDstu3TerminologyTest.java | 4 +- .../r4/FhirResourceDaoR4TerminologyTest.java | 2 +- .../r4/ResourceProviderR4ValueSetTest.java | 46 +++++++ .../term/TermCodeSystemStorageSvcTest.java | 27 +++- .../term/TerminologyLoaderSvcLoincTest.java | 30 ++++- .../jpa/term/TerminologySvcDeltaR4Test.java | 2 +- .../jpa/term/TerminologySvcImplR4Test.java | 114 ++++++++++++++++ .../loinc/v267_loincupload.properties | 87 +++++++++++++ .../loinc/v268_loincupload.properties | 87 +++++++++++++ .../support/BaseValidationSupportWrapper.java | 5 + .../support/CachingValidationSupport.java | 6 + .../CommonCodeSystemsTerminologyService.java | 6 + ...oryTerminologyServerValidationSupport.java | 9 +- .../support/ValidationSupportChain.java | 10 ++ 35 files changed, 742 insertions(+), 102 deletions(-) create mode 100644 hapi-fhir-jpaserver-base/src/test/resources/loinc/v267_loincupload.properties create mode 100644 hapi-fhir-jpaserver-base/src/test/resources/loinc/v268_loincupload.properties diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java index 49739e62b1e..a1efd061a7c 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java @@ -183,6 +183,24 @@ public interface IValidationSupport { return null; } + /** + * Validates that the given code exists and if possible returns a display + * name. This method is called to check codes which are found in "example" + * binding fields (e.g. Observation.code in the default profile. + * + * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to + * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. + * @param theOptions Provides options controlling the validation + * @param theCodeSystem The code system, e.g. "http://loinc.org" + * @param theCode The code, e.g. "1234-5" + * @param theDisplay The display name, if it should also be validated + * @param theSystemVersion The code system version. + * @return Returns a validation result object + */ + default CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl, String theSystemVersion) { + return null; + } + /** * Validates that the given code exists and if possible returns a display * name. This method is called to check codes which are found in "example" @@ -212,6 +230,19 @@ public interface IValidationSupport { return null; } + /** + * Look up a code using the system, system version and code value + * + * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to + * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. + * @param theSystem The CodeSystem URL + * @param theCode The code + * @param theSystemVersion The CodeSystem version + */ + default LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theSystemVersion) { + return null; + } + /** * Returns true if the given valueset can be validated by the given * validation support module diff --git a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/config/DaoConfig.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/config/DaoConfig.java index 98f49f6ff2d..85b6bb6b8ca 100644 --- a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/config/DaoConfig.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/config/DaoConfig.java @@ -227,6 +227,11 @@ public class DaoConfig { */ private boolean myPreloadBlobFromInputStream = false; + /** + * @since 5.1.0 + */ + private boolean myMultipleCodeSystemVersionsEnabled = false; + /** * Constructor */ @@ -2126,4 +2131,34 @@ public class DaoConfig { myPreloadBlobFromInputStream = thePreloadBlobFromInputStream; } + /** + *

+ * This determines whether multiple code system versions will be enabled. If not enabled, existing code systems will be + * deleted when a new version is uploaded. + *

+ *

+ * The default value for this setting is {@code false}. + *

+ * + * @since 5.1.0 + */ + public boolean isMultipleCodeSystemVersionsEnabled() { + return myMultipleCodeSystemVersionsEnabled; + } + + /** + *

+ * This determines whether multiple code system versions will be enabled. If not enabled, existing code systems will be + * deleted when a new version is uploaded. + *

+ *

+ * The default value for this setting is {@code false}. + *

+ * + * @since 5.1.0 + */ + public void setMultipleCodeSystemVersionsEnabled(Boolean theMultipleCodeSystemVersionsEnabled) { + myMultipleCodeSystemVersionsEnabled = theMultipleCodeSystemVersionsEnabled; + } + } diff --git a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoCodeSystem.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoCodeSystem.java index ba957e00542..2c00c792b49 100644 --- a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoCodeSystem.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDaoCodeSystem.java @@ -42,6 +42,11 @@ public interface IFhirResourceDaoCodeSystem ext SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, CD theCodingA, CD theCodingB, RequestDetails theRequestDetails); + @Nonnull + IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType theCode, IPrimitiveType theSystem, CD theCoding, IPrimitiveType theVersion, RequestDetails theRequestDetails); + + SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, CD theCodingA, CD theCodingB, IPrimitiveType theVersion, RequestDetails theRequestDetails); + class SubsumesResult { private final ConceptSubsumptionOutcome myOutcome; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2.java index ec2b7bd27e6..c43d3e3d9c2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2.java @@ -48,6 +48,7 @@ import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport; import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -235,6 +236,12 @@ public class FhirResourceDaoValueSetDstu2 extends BaseHapiFhirResourceDao theCode, IPrimitiveType theSystem, CodingDt theCoding, IPrimitiveType theVersion, RequestDetails theRequestDetails) { + throw new UnsupportedOperationException(); + } + @Override public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType theCode, IPrimitiveType theSystem, CodingDt theCoding, RequestDetails theRequest) { boolean haveCoding = theCoding != null && isNotBlank(theCoding.getSystem()) && isNotBlank(theCoding.getCode()); @@ -280,6 +287,11 @@ public class FhirResourceDaoValueSetDstu2 extends BaseHapiFhirResourceDao theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, CodingDt theCodingA, CodingDt theCodingB, IPrimitiveType theVersion, RequestDetails theRequestDetails) { + throw new UnsupportedOperationException(); + } + @Override @PostConstruct public void postConstruct() { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermCodeSystemVersionDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermCodeSystemVersionDao.java index 92af07eaef9..fc55636836f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermCodeSystemVersionDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ITermCodeSystemVersionDao.java @@ -35,9 +35,19 @@ public interface ITermCodeSystemVersionDao extends JpaRepository findByCodeSystemPid(@Param("codesystem_pid") Long theCodeSystemPid); + @Query("SELECT cs FROM TermCodeSystemVersion cs WHERE cs.myCodeSystemPid = :codesystem_pid AND cs.myCodeSystemVersionId = :codesystem_version_id") + TermCodeSystemVersion findByCodeSystemPidAndVersion(@Param("codesystem_pid") Long theCodeSystemPid, @Param("codesystem_version_id") String theCodeSystemVersionId); + + @Query("SELECT cs FROM TermCodeSystemVersion cs WHERE cs.myCodeSystemPid = :codesystem_pid AND cs.myCodeSystemVersionId IS NULL") + TermCodeSystemVersion findByCodeSystemPidVersionIsNull(@Param("codesystem_pid") Long theCodeSystemPid); + @Query("SELECT cs FROM TermCodeSystemVersion cs WHERE cs.myResourcePid = :resource_id") List findByCodeSystemResourcePid(@Param("resource_id") Long theCodeSystemResourcePid); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java index 0fbd43ac94f..6d2cd77aa99 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java @@ -44,6 +44,7 @@ import org.hl7.fhir.dstu3.model.Coding; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import javax.annotation.Nonnull; @@ -86,9 +87,15 @@ public class FhirResourceDaoCodeSystemDstu3 extends BaseHapiFhirResourceDao theCode, IPrimitiveType theSystem, Coding theCoding, RequestDetails theRequestDetails) { + return lookupCode(theCode, theSystem, theCoding, null, theRequestDetails); + } + + @Nonnull + @Override + public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType theCode, IPrimitiveType theSystem, Coding theCoding, IPrimitiveType theVersion, RequestDetails theRequestDetails) { boolean haveCoding = theCoding != null && isNotBlank(theCoding.getSystem()) && isNotBlank(theCoding.getCode()); boolean haveCode = theCode != null && theCode.isEmpty() == false; boolean haveSystem = theSystem != null && theSystem.isEmpty() == false; @@ -109,12 +116,16 @@ public class FhirResourceDaoCodeSystemDstu3 extends BaseHapiFhirResourceDao theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, Coding theCodingA, Coding theCodingB, IPrimitiveType theVersion, RequestDetails theRequestDetails) { + return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theVersion); + } + @Override public SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, Coding theCodingA, Coding theCodingB, RequestDetails theRequestDetails) { return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java index f2c7f221e30..a7f91e3c1b0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java @@ -44,6 +44,7 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import javax.annotation.Nonnull; @@ -81,9 +82,15 @@ public class FhirResourceDaoCodeSystemR4 extends BaseHapiFhirResourceDao theCode, IPrimitiveType theSystem, Coding theCoding, RequestDetails theRequestDetails) { + return lookupCode(theCode, theSystem, theCoding, null, theRequestDetails); + } + + @Nonnull + @Override + public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType theCode, IPrimitiveType theSystem, Coding theCoding, IPrimitiveType theVersion, RequestDetails theRequestDetails) { boolean haveCoding = theCoding != null && isNotBlank(theCoding.getSystem()) && isNotBlank(theCoding.getCode()); boolean haveCode = theCode != null && theCode.isEmpty() == false; boolean haveSystem = theSystem != null && theSystem.isEmpty() == false; @@ -104,13 +111,17 @@ public class FhirResourceDaoCodeSystemR4 extends BaseHapiFhirResourceDao theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, Coding theCodingA, Coding theCodingB, IPrimitiveType theVersion, RequestDetails theRequestDetails) { + return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theVersion); + } + @Override public SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, Coding theCodingA, Coding theCodingB, RequestDetails theRequestDetails) { return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java index c2170f8ac7a..7a104f1ba97 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoValueSetR4.java @@ -113,14 +113,17 @@ public class FhirResourceDaoValueSetR4 extends BaseHapiFhirResourceDao ValueSet source = new ValueSet(); source.setUrl(theUri); - source.getCompose().addInclude().addValueSet(theUri); +// source.getCompose().addInclude().addValueSet(theUri); if (isNotBlank(theFilter)) { - ConceptSetComponent include = source.getCompose().addInclude(); - ConceptSetFilterComponent filter = include.addFilter(); +// ConceptSetComponent include = source.getCompose().addInclude(); +// ConceptSetFilterComponent filter = include.addFilter(); + ConceptSetFilterComponent filter = source.getCompose().addInclude().addValueSet(theUri).addFilter(); filter.setProperty("display"); filter.setOp(FilterOperator.EQUAL); filter.setValue(theFilter); + } else { + source.getCompose().addInclude().addValueSet(theUri); } ValueSet retVal = doExpand(source); @@ -148,14 +151,17 @@ public class FhirResourceDaoValueSetR4 extends BaseHapiFhirResourceDao ValueSet source = new ValueSet(); source.setUrl(theUri); - source.getCompose().addInclude().addValueSet(theUri); +// source.getCompose().addInclude().addValueSet(theUri); if (isNotBlank(theFilter)) { - ConceptSetComponent include = source.getCompose().addInclude(); - ConceptSetFilterComponent filter = include.addFilter(); +// ConceptSetComponent include = source.getCompose().addInclude(); +// ConceptSetFilterComponent filter = include.addFilter(); + ConceptSetFilterComponent filter = source.getCompose().addInclude().addValueSet(theUri).addFilter(); filter.setProperty("display"); filter.setOp(FilterOperator.EQUAL); filter.setValue(theFilter); + } else { + source.getCompose().addInclude().addValueSet(theUri); } ValueSet retVal = doExpand(source, theOffset, theCount); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java index 0cc481ae32c..329cf00eb40 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java @@ -45,6 +45,7 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.CodeableConcept; import org.hl7.fhir.r5.model.Coding; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import javax.annotation.Nonnull; @@ -83,9 +84,15 @@ public class FhirResourceDaoCodeSystemR5 extends BaseHapiFhirResourceDao theCode, IPrimitiveType theSystem, Coding theCoding, RequestDetails theRequestDetails) { + return lookupCode(theCode, theSystem, theCoding, null, theRequestDetails); + } + + @Nonnull + @Override + public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType theCode, IPrimitiveType theSystem, Coding theCoding, IPrimitiveType theVersion, RequestDetails theRequestDetails) { boolean haveCoding = theCoding != null && isNotBlank(theCoding.getSystem()) && isNotBlank(theCoding.getCode()); boolean haveCode = theCode != null && theCode.isEmpty() == false; boolean haveSystem = theSystem != null && theSystem.isEmpty() == false; @@ -106,13 +113,17 @@ public class FhirResourceDaoCodeSystemR5 extends BaseHapiFhirResourceDao theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, Coding theCodingA, Coding theCodingB, IPrimitiveType theVersion, RequestDetails theRequestDetails) { + return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theVersion); + } + @Override public SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, Coding theCodingA, Coding theCodingB, RequestDetails theRequestDetails) { return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystemVersion.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystemVersion.java index 5b16f5811e9..04444c47f3f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystemVersion.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermCodeSystemVersion.java @@ -40,17 +40,21 @@ import javax.persistence.OneToMany; import javax.persistence.OneToOne; import javax.persistence.SequenceGenerator; import javax.persistence.Table; +import javax.persistence.UniqueConstraint; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import static org.apache.commons.lang3.StringUtils.length; -@Table(name = "TRM_CODESYSTEM_VER" +@Table(name = "TRM_CODESYSTEM_VER", // Note, we used to have a constraint named IDX_CSV_RESOURCEPID_AND_VER (don't reuse this) -) + uniqueConstraints = { + @UniqueConstraint(name = TermCodeSystemVersion.IDX_CODESYSTEM_AND_VER, columnNames = {"CODESYSTEM_PID", "CS_VERSION_ID"}) +}) @Entity() public class TermCodeSystemVersion implements Serializable { + public static final String IDX_CODESYSTEM_AND_VER = "IDX_CODESYSTEM_AND_VER"; public static final int MAX_VERSION_LENGTH = 200; private static final long serialVersionUID = 1L; @OneToMany(fetch = FetchType.LAZY, mappedBy = "myCodeSystem") diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderCodeSystemDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderCodeSystemDstu3.java index 2fcee77131e..a6d061c3e06 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderCodeSystemDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderCodeSystemDstu3.java @@ -53,6 +53,7 @@ public class BaseJpaResourceProviderCodeSystemDstu3 extends JpaResourceProviderD @OperationParam(name = "code", min = 0, max = 1) CodeType theCode, @OperationParam(name = "system", min = 0, max = 1) UriType theSystem, @OperationParam(name = "coding", min = 0, max = 1) Coding theCoding, + @OperationParam(name="version", min=0, max=1) StringType theVersion, @OperationParam(name = "property", min = 0, max = OperationParam.MAX_UNLIMITED) List theProperties, RequestDetails theRequestDetails ) { @@ -60,7 +61,7 @@ public class BaseJpaResourceProviderCodeSystemDstu3 extends JpaResourceProviderD startRequest(theServletRequest); try { IFhirResourceDaoCodeSystem dao = (IFhirResourceDaoCodeSystem) getDao(); - IValidationSupport.LookupCodeResult result = dao.lookupCode(theCode, theSystem, theCoding, theRequestDetails); + IValidationSupport.LookupCodeResult result = dao.lookupCode(theCode, theSystem, theCoding, theVersion, theRequestDetails); result.throwNotFoundIfAppropriate(); return (Parameters) result.toParameters(theRequestDetails.getFhirContext(), theProperties); } catch (FHIRException e) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderCodeSystemR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderCodeSystemR4.java index 2c69bdbf54c..dec2d1adbcc 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderCodeSystemR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderCodeSystemR4.java @@ -55,6 +55,7 @@ public class BaseJpaResourceProviderCodeSystemR4 extends JpaResourceProviderR4 theProperties, RequestDetails theRequestDetails ) { @@ -62,7 +63,7 @@ public class BaseJpaResourceProviderCodeSystemR4 extends JpaResourceProviderR4 dao = (IFhirResourceDaoCodeSystem) getDao(); - IValidationSupport.LookupCodeResult result = dao.lookupCode(theCode, theSystem, theCoding, theRequestDetails); + IValidationSupport.LookupCodeResult result = dao.lookupCode(theCode, theSystem, theCoding, theVersion, theRequestDetails); result.throwNotFoundIfAppropriate(); return (Parameters) result.toParameters(theRequestDetails.getFhirContext(), theProperties); } finally { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCodeSystemR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCodeSystemR5.java index 38545e7d5d7..45d80b0228f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCodeSystemR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCodeSystemR5.java @@ -55,6 +55,7 @@ public class BaseJpaResourceProviderCodeSystemR5 extends JpaResourceProviderR5 theProperties, RequestDetails theRequestDetails ) { @@ -62,7 +63,7 @@ public class BaseJpaResourceProviderCodeSystemR5 extends JpaResourceProviderR5 dao = (IFhirResourceDaoCodeSystem) getDao(); - IValidationSupport.LookupCodeResult result = dao.lookupCode(theCode, theSystem, theCoding, theRequestDetails); + IValidationSupport.LookupCodeResult result = dao.lookupCode(theCode, theSystem, theCoding, theVersion, theRequestDetails); result.throwNotFoundIfAppropriate(); return (Parameters) result.toParameters(theRequestDetails.getFhirContext(), theProperties); } finally { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java index b03dc4aec20..69e1113fb09 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java @@ -244,7 +244,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { @Override public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { - TermCodeSystemVersion cs = getCurrentCodeSystemVersion(theSystem); + TermCodeSystemVersion cs = getCurrentCodeSystemVersionForVersion(theSystem,null); return cs != null; } @@ -616,6 +616,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { private Boolean expandValueSetHandleIncludeOrExclude(@Nullable ValueSetExpansionOptions theExpansionOptions, IValueSetConceptAccumulator theValueSetCodeAccumulator, Set theAddedCodes, ValueSet.ConceptSetComponent theIncludeOrExclude, boolean theAdd, AtomicInteger theCodeCounter, int theQueryIndex, VersionIndependentConcept theWantConceptOrNull) { String system = theIncludeOrExclude.getSystem(); + String systemVersion = theIncludeOrExclude.getVersion(); boolean hasSystem = isNotBlank(system); boolean hasValueSet = theIncludeOrExclude.getValueSet().size() > 0; @@ -630,7 +631,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(system); if (cs != null) { - return expandValueSetHandleIncludeOrExcludeUsingDatabase(theValueSetCodeAccumulator, theAddedCodes, theIncludeOrExclude, theAdd, theCodeCounter, theQueryIndex, theWantConceptOrNull, system, cs); + return expandValueSetHandleIncludeOrExcludeUsingDatabase(theValueSetCodeAccumulator, theAddedCodes, theIncludeOrExclude, theAdd, theCodeCounter, theQueryIndex, theWantConceptOrNull, system, cs, systemVersion); } else { @@ -671,7 +672,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { nextSystem = system; } - LookupCodeResult lookup = myValidationSupport.lookupCode(new ValidationSupportContext(provideValidationSupport()), nextSystem, next.getCode()); + LookupCodeResult lookup = myValidationSupport.lookupCode(new ValidationSupportContext(provideValidationSupport()), nextSystem, next.getCode(), systemVersion); if (lookup != null && lookup.isFound()) { addOrRemoveCode(theValueSetCodeAccumulator, theAddedCodes, theAdd, nextSystem, next.getCode(), lookup.getCodeDisplay()); foundCount++; @@ -765,7 +766,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } @Nonnull - private Boolean expandValueSetHandleIncludeOrExcludeUsingDatabase(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set theAddedCodes, ValueSet.ConceptSetComponent theIncludeOrExclude, boolean theAdd, AtomicInteger theCodeCounter, int theQueryIndex, VersionIndependentConcept theWantConceptOrNull, String theSystem, TermCodeSystem theCs) { + private Boolean expandValueSetHandleIncludeOrExcludeUsingDatabase(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set theAddedCodes, ValueSet.ConceptSetComponent theIncludeOrExclude, boolean theAdd, AtomicInteger theCodeCounter, int theQueryIndex, VersionIndependentConcept theWantConceptOrNull, String theSystem, TermCodeSystem theCs, String theSystemVersion) { TermCodeSystemVersion csv = theCs.getCurrentVersion(); FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager); @@ -793,7 +794,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { /* * Filters */ - handleFilters(bool, theSystem, qb, theIncludeOrExclude); + handleFilters(bool, theSystem, qb, theIncludeOrExclude, theSystemVersion); Query luceneQuery = bool.createQuery(); @@ -902,15 +903,15 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } } - private void handleFilters(BooleanJunction theBool, String theSystem, QueryBuilder theQb, ValueSet.ConceptSetComponent theIncludeOrExclude) { + private void handleFilters(BooleanJunction theBool, String theSystem, QueryBuilder theQb, ValueSet.ConceptSetComponent theIncludeOrExclude, String theSystemVersion) { if (theIncludeOrExclude.getFilter().size() > 0) { for (ValueSet.ConceptSetFilterComponent nextFilter : theIncludeOrExclude.getFilter()) { - handleFilter(theSystem, theQb, theBool, nextFilter); + handleFilter(theSystem, theQb, theBool, nextFilter, theSystemVersion); } } } - private void handleFilter(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { + private void handleFilter(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter, String theSystemVersion) { if (isBlank(theFilter.getValue()) && theFilter.getOp() == null && isBlank(theFilter.getProperty())) { return; } @@ -926,7 +927,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { break; case "concept": case "code": - handleFilterConceptAndCode(theSystem, theQb, theBool, theFilter); + handleFilterConceptAndCode(theSystem, theQb, theBool, theFilter, theSystemVersion); break; case "parent": case "child": @@ -935,11 +936,11 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { break; case "ancestor": isCodeSystemLoingOrThrowInvalidRequestException(theSystem, theFilter.getProperty()); - handleFilterLoincAncestor(theSystem, theQb, theBool, theFilter); + handleFilterLoincAncestor(theSystem, theQb, theBool, theFilter, theSystemVersion); break; case "descendant": isCodeSystemLoingOrThrowInvalidRequestException(theSystem, theFilter.getProperty()); - handleFilterLoincDescendant(theSystem, theQb, theBool, theFilter); + handleFilterLoincDescendant(theSystem, theQb, theBool, theFilter, theSystemVersion); break; case "copyright": isCodeSystemLoingOrThrowInvalidRequestException(theSystem, theFilter.getProperty()); @@ -990,8 +991,8 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { bool.must(textQuery); } - private void handleFilterConceptAndCode(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { - TermConcept code = findCode(theSystem, theFilter.getValue()) + private void handleFilterConceptAndCode(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter, String theSystemVersion) { + TermConcept code = findCode(theSystem, theFilter.getValue(), theSystemVersion) .orElseThrow(() -> new InvalidRequestException("Invalid filter criteria - code does not exist: {" + Constants.codeSystemWithDefaultDescription(theSystem) + "}" + theFilter.getValue())); if (theFilter.getOp() == ValueSet.FilterOperator.ISA) { @@ -1036,41 +1037,41 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } @SuppressWarnings("EnumSwitchStatementWhichMissesCases") - private void handleFilterLoincAncestor(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { + private void handleFilterLoincAncestor(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter, String theSystemVersion) { switch (theFilter.getOp()) { case EQUAL: - addLoincFilterAncestorEqual(theSystem, theQb, theBool, theFilter); + addLoincFilterAncestorEqual(theSystem, theQb, theBool, theFilter, theSystemVersion); break; case IN: - addLoincFilterAncestorIn(theSystem, theQb, theBool, theFilter); + addLoincFilterAncestorIn(theSystem, theQb, theBool, theFilter, theSystemVersion); break; default: throw new InvalidRequestException("Don't know how to handle op=" + theFilter.getOp() + " on property " + theFilter.getProperty()); } } - private void addLoincFilterAncestorEqual(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { - addLoincFilterAncestorEqual(theSystem, theQb, theBool, theFilter.getProperty(), theFilter.getValue()); + private void addLoincFilterAncestorEqual(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter, String theSystemVersion) { + addLoincFilterAncestorEqual(theSystem, theQb, theBool, theFilter.getProperty(), theFilter.getValue(), theSystemVersion); } - private void addLoincFilterAncestorEqual(String theSystem, QueryBuilder theQb, BooleanJunction theBool, String theProperty, String theValue) { - List terms = getAncestorTerms(theSystem, theProperty, theValue); + private void addLoincFilterAncestorEqual(String theSystem, QueryBuilder theQb, BooleanJunction theBool, String theProperty, String theValue, String theSystemVersion) { + List terms = getAncestorTerms(theSystem, theProperty, theValue, theSystemVersion); theBool.must(new TermsQuery(terms)); } - private void addLoincFilterAncestorIn(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { + private void addLoincFilterAncestorIn(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter, String theSystemVersion) { String[] values = theFilter.getValue().split(","); List terms = new ArrayList<>(); for (String value : values) { - terms.addAll(getAncestorTerms(theSystem, theFilter.getProperty(), value)); + terms.addAll(getAncestorTerms(theSystem, theFilter.getProperty(), value, theSystemVersion)); } theBool.must(new TermsQuery(terms)); } - private List getAncestorTerms(String theSystem, String theProperty, String theValue) { + private List getAncestorTerms(String theSystem, String theProperty, String theValue, String theSystemVersion) { List retVal = new ArrayList<>(); - TermConcept code = findCode(theSystem, theValue) + TermConcept code = findCode(theSystem, theValue, theSystemVersion) .orElseThrow(() -> new InvalidRequestException("Invalid filter criteria - code does not exist: {" + Constants.codeSystemWithDefaultDescription(theSystem) + "}" + theValue)); retVal.add(new Term("myParentPids", "" + code.getId())); @@ -1080,41 +1081,41 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } @SuppressWarnings("EnumSwitchStatementWhichMissesCases") - private void handleFilterLoincDescendant(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { + private void handleFilterLoincDescendant(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter, String theSystemVersion) { switch (theFilter.getOp()) { case EQUAL: - addLoincFilterDescendantEqual(theSystem, theBool, theFilter); + addLoincFilterDescendantEqual(theSystem, theBool, theFilter, theSystemVersion); break; case IN: - addLoincFilterDescendantIn(theSystem, theQb, theBool, theFilter); + addLoincFilterDescendantIn(theSystem, theQb, theBool, theFilter, theSystemVersion); break; default: throw new InvalidRequestException("Don't know how to handle op=" + theFilter.getOp() + " on property " + theFilter.getProperty()); } } - private void addLoincFilterDescendantEqual(String theSystem, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { - addLoincFilterDescendantEqual(theSystem, theBool, theFilter.getProperty(), theFilter.getValue()); + private void addLoincFilterDescendantEqual(String theSystem, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter, String theSystemVersion) { + addLoincFilterDescendantEqual(theSystem, theBool, theFilter.getProperty(), theFilter.getValue(), theSystemVersion); } - private void addLoincFilterDescendantEqual(String theSystem, BooleanJunction theBool, String theProperty, String theValue) { - List terms = getDescendantTerms(theSystem, theProperty, theValue); + private void addLoincFilterDescendantEqual(String theSystem, BooleanJunction theBool, String theProperty, String theValue, String theSystemVersion) { + List terms = getDescendantTerms(theSystem, theProperty, theValue, theSystemVersion); theBool.must(new TermsQuery(terms)); } - private void addLoincFilterDescendantIn(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { + private void addLoincFilterDescendantIn(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter, String theSystemVersion) { String[] values = theFilter.getValue().split(","); List terms = new ArrayList<>(); for (String value : values) { - terms.addAll(getDescendantTerms(theSystem, theFilter.getProperty(), value)); + terms.addAll(getDescendantTerms(theSystem, theFilter.getProperty(), value, theSystemVersion)); } theBool.must(new TermsQuery(terms)); } - private List getDescendantTerms(String theSystem, String theProperty, String theValue) { + private List getDescendantTerms(String theSystem, String theProperty, String theValue, String theSystemVersion) { List retVal = new ArrayList<>(); - TermConcept code = findCode(theSystem, theValue) + TermConcept code = findCode(theSystem, theValue, theSystemVersion) .orElseThrow(() -> new InvalidRequestException("Invalid filter criteria - code does not exist: {" + Constants.codeSystemWithDefaultDescription(theSystem) + "}" + theValue)); String[] parentPids = code.getParentPidsAsString().split(" "); @@ -1363,6 +1364,12 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { @Override public Optional findCode(String theCodeSystem, String theCode) { + return findCode(theCodeSystem, theCode, null); + + } + + @Override + public Optional findCode(String theCodeSystem, String theCode, String theVersion) { /* * Loading concepts without a transaction causes issues later on some * platforms (e.g. PSQL) so this transactiontemplate is here to make @@ -1371,7 +1378,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_MANDATORY); return txTemplate.execute(t -> { - TermCodeSystemVersion csv = getCurrentCodeSystemVersion(theCodeSystem); + TermCodeSystemVersion csv = getCurrentCodeSystemVersionForVersion(theCodeSystem, theVersion); if (csv == null) { return null; } @@ -1380,12 +1387,20 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } @Nullable - private TermCodeSystemVersion getCurrentCodeSystemVersion(String theUri) { - TermCodeSystemVersion retVal = myCodeSystemCurrentVersionCache.get(theUri, uri -> myTxTemplate.execute(tx -> { + private TermCodeSystemVersion getCurrentCodeSystemVersionForVersion(String theUri, String theVersion) { + StringBuffer key = new StringBuffer(theUri); + if (theVersion != null) { + key.append("_").append(theVersion); + } + TermCodeSystemVersion retVal = myCodeSystemCurrentVersionCache.get(key.toString(), uri -> myTxTemplate.execute(tx -> { TermCodeSystemVersion csv = null; TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(uri); - if (cs != null && cs.getCurrentVersion() != null) { - csv = cs.getCurrentVersion(); + if (cs != null) { + if (theVersion != null) { + csv = myCodeSystemVersionDao.findByCodeSystemPidAndVersion(cs.getPid(), theVersion); + } else if (cs.getCurrentVersion() != null) { + csv = cs.getCurrentVersion(); + } } if (csv != null) { return csv; @@ -1804,19 +1819,28 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } @Override - @Transactional public IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB) { + return subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, null); + } + + @Override + @Transactional + public IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB, IPrimitiveType theSystemVersion) { VersionIndependentConcept conceptA = toConcept(theCodeA, theSystem, theCodingA); VersionIndependentConcept conceptB = toConcept(theCodeB, theSystem, theCodingB); + String systemVersion = null; + if (theSystemVersion != null) { + systemVersion = theSystemVersion.getValue(); + } if (!StringUtils.equals(conceptA.getSystem(), conceptB.getSystem())) { throw new InvalidRequestException("Unable to test subsumption across different code systems"); } - TermConcept codeA = findCode(conceptA.getSystem(), conceptA.getCode()) + TermConcept codeA = findCode(conceptA.getSystem(), conceptA.getCode(), systemVersion) .orElseThrow(() -> new InvalidRequestException("Unknown code: " + conceptA)); - TermConcept codeB = findCode(conceptB.getSystem(), conceptB.getCode()) + TermConcept codeB = findCode(conceptB.getSystem(), conceptB.getCode(), systemVersion) .orElseThrow(() -> new InvalidRequestException("Unknown code: " + conceptB)); FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager); @@ -1835,10 +1859,10 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { protected abstract ValueSet toCanonicalValueSet(IBaseResource theValueSet); - protected IValidationSupport.LookupCodeResult lookupCode(String theSystem, String theCode) { + protected IValidationSupport.LookupCodeResult lookupCode(String theSystem, String theCode, String theVersion) { TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); return txTemplate.execute(t -> { - Optional codeOpt = findCode(theSystem, theCode); + Optional codeOpt = findCode(theSystem, theCode, theVersion); if (codeOpt.isPresent()) { TermConcept code = codeOpt.get(); @@ -2107,10 +2131,14 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { return null; } - @CoverageIgnore @Override public IValidationSupport.CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { + return validateCode(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, theValueSetUrl, null); + } + + @Override + public IValidationSupport.CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl, String theSystemVersion) { invokeRunnableForUnitTest(); if (isNotBlank(theValueSetUrl)) { @@ -2119,7 +2147,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); - Optional codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> new VersionIndependentConcept(theCodeSystem, c.getCode()))); + Optional codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode, theSystemVersion).map(c -> new VersionIndependentConcept(theCodeSystem, c.getCode()))); if (codeOpt != null && codeOpt.isPresent()) { VersionIndependentConcept code = codeOpt.get(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java index 6775fd6f2f0..13683722d6b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java @@ -284,16 +284,19 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { ResourcePersistentId codeSystemResourcePid = getCodeSystemResourcePid(theCodeSystem.getIdElement()); /* - * If this is a not-present codesystem, we don't want to store a new version if one - * already exists, since that will wipe out the existing concepts. We do create or update - * the TermCodeSystem table though, since that allows the DB to reject changes - * that would result in duplicate CodeSysten.url values. + * If this is a not-present codesystem and codesystem version already exists, we don't want to + * overwrite the existing version since that will wipe out the existing concepts. We do create + * or update the TermCodeSystem table though, since that allows the DB to reject changes that would + * result in duplicate CodeSystem.url values. */ if (theCodeSystem.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT) { - TermCodeSystem codeSystem = myCodeSystemDao.findByCodeSystemUri(theCodeSystem.getUrl()); - if (codeSystem != null) { - getOrCreateTermCodeSystem(codeSystemResourcePid, theCodeSystem.getUrl(), theCodeSystem.getUrl(), theResourceEntity); - return; + TermCodeSystem termCodeSystem = myCodeSystemDao.findByCodeSystemUri(theCodeSystem.getUrl()); + if (termCodeSystem != null) { + TermCodeSystemVersion codeSystemVersion = getExistingTermCodeSystemVersion(termCodeSystem.getPid(), theCodeSystem.getVersion()); + if (codeSystemVersion != null) { + getOrCreateTermCodeSystem(codeSystemResourcePid, theCodeSystem.getUrl(), theCodeSystem.getUrl(), theResourceEntity); + return; + } } } @@ -338,22 +341,23 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { ValidateUtil.isTrueOrThrowInvalidRequest(theCodeSystemVersion.getResource() != null, "No resource supplied"); ValidateUtil.isNotBlankOrThrowInvalidRequest(theSystemUri, "No system URI supplied"); - // Grab the existing versions so we can delete them later - List existing = myCodeSystemVersionDao.findByCodeSystemResourcePid(theCodeSystemResourcePid.getIdAsLong()); - - /* - * For now we always delete old versions. At some point it would be nice to allow configuration to keep old versions. - */ - - for (TermCodeSystemVersion next : existing) { - ourLog.info("Deleting old code system version {}", next.getPid()); - Long codeSystemVersionPid = next.getPid(); - deleteCodeSystemVersion(codeSystemVersionPid); + // Grab the existing version so we can delete it + TermCodeSystem existingCodeSystem = myCodeSystemDao.findByCodeSystemUri(theSystemUri); + TermCodeSystemVersion existing = null; + if (existingCodeSystem != null) { + existing = getExistingTermCodeSystemVersion(existingCodeSystem.getPid(), theSystemVersionId); } - ourLog.debug("Flushing..."); - myConceptDao.flush(); - ourLog.debug("Done flushing"); + /* + * Delete version being replaced. + */ + + if(existing != null) { + ourLog.info("Deleting old code system version {}", existing.getPid()); + Long codeSystemVersionPid = existing.getPid(); + deleteCodeSystemVersion(codeSystemVersionPid); + + } /* * Do the upload @@ -408,6 +412,17 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { } } + private TermCodeSystemVersion getExistingTermCodeSystemVersion(Long theCodeSystemVersionPid, String theCodeSystemVersion) { + TermCodeSystemVersion existing; + if (theCodeSystemVersion == null) { + existing = myCodeSystemVersionDao.findByCodeSystemPidVersionIsNull(theCodeSystemVersionPid); + } else { + existing = myCodeSystemVersionDao.findByCodeSystemPidAndVersion(theCodeSystemVersionPid, theCodeSystemVersion); + } + + return existing; + } + private void deleteCodeSystemVersion(final Long theCodeSystemVersionPid) { ourLog.info(" * Deleting code system version {}", theCodeSystemVersionPid); @@ -457,8 +472,11 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { myCodeSystemDao.save(codeSystem); } + myConceptDao.flush(); + ourLog.info(" * Deleting code system version"); - myCodeSystemVersionDao.deleteById(theCodeSystemVersionPid); + myCodeSystemVersionDao.delete(theCodeSystemVersionPid); + myCodeSystemVersionDao.flush(); }); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermLoaderSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermLoaderSvcImpl.java index 11401f845a1..3fe945458a5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermLoaderSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermLoaderSvcImpl.java @@ -378,6 +378,10 @@ public class TermLoaderSvcImpl implements ITermLoaderSvc { try { String loincCsString = IOUtils.toString(BaseTermReadSvcImpl.class.getResourceAsStream("/ca/uhn/fhir/jpa/term/loinc/loinc.xml"), Charsets.UTF_8); loincCs = FhirContext.forR4().newXmlParser().parseResource(CodeSystem.class, loincCsString); + String codeSystemVersionId = theUploadProperties.getProperty(LOINC_CODESYSTEM_VERSION.getCode()); + if (codeSystemVersionId != null) { + loincCs.setVersion(codeSystemVersionId); + } } catch (IOException e) { throw new InternalErrorException("Failed to load loinc.xml", e); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu3.java index 6b266ae52f3..0056824d586 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcDstu3.java @@ -149,7 +149,12 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation @Override public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { - return super.lookupCode(theSystem, theCode); + return super.lookupCode(theSystem, theCode, null); + } + + @Override + public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theVersion) { + return super.lookupCode(theSystem, theCode, theVersion); } @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR4.java index 2075725dc2f..96243af965a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcR4.java @@ -89,9 +89,14 @@ public class TermReadSvcR4 extends BaseTermReadSvcImpl implements ITermReadSvcR4 return (CodeSystem) theCodeSystem; } + @Override + public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theVersion) { + return super.lookupCode(theSystem, theCode, theVersion); + } + @Override public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { - return super.lookupCode(theSystem, theCode); + return super.lookupCode(theSystem, theCode, null); } @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java index bfac6ae33d9..9e6e340e5bd 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java @@ -73,6 +73,8 @@ public interface ITermReadSvc extends IValidationSupport { List expandValueSet(ValueSetExpansionOptions theExpansionOptions, String theValueSet); + Optional findCode(String theCodeSystem, String theCode, String theVersion); + Optional findCode(String theCodeSystem, String theCode); Set findCodesAbove(Long theCodeSystemResourcePid, Long theCodeSystemResourceVersionPid, String theCode); @@ -103,6 +105,8 @@ public interface ITermReadSvc extends IValidationSupport { IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB); + IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB, IPrimitiveType theSystemVersion); + void preExpandDeferredValueSetsToTerminologyTables(); /** diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincUploadPropertiesEnum.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincUploadPropertiesEnum.java index d1d1295c9e2..225601034b7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincUploadPropertiesEnum.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincUploadPropertiesEnum.java @@ -96,6 +96,9 @@ public enum LoincUploadPropertiesEnum { /* * OPTIONAL */ + // This is the version identifier for the answer list file + LOINC_CODESYSTEM_VERSION("loinc.codesystem.version"), + // This is the version identifier for the answer list file LOINC_ANSWERLIST_VERSION("loinc.answerlist.version"), diff --git a/hapi-fhir-jpaserver-base/src/main/resources/ca/uhn/fhir/jpa/term/loinc/loincupload.properties b/hapi-fhir-jpaserver-base/src/main/resources/ca/uhn/fhir/jpa/term/loinc/loincupload.properties index 986d2afdaa0..cb7d1eb0345 100644 --- a/hapi-fhir-jpaserver-base/src/main/resources/ca/uhn/fhir/jpa/term/loinc/loincupload.properties +++ b/hapi-fhir-jpaserver-base/src/main/resources/ca/uhn/fhir/jpa/term/loinc/loincupload.properties @@ -61,6 +61,10 @@ loinc.universal.lab.order.valueset.file=AccessoryFiles/LoincUniversalLabOrdersVa ### OPTIONAL ### ################ +# This is the version identifier for the LOINC code system +## Key may be omitted if only a single version of LOINC is being kept. +loinc.codesystem.version=2.68 + # This is the version identifier for the answer list file ## Key may be omitted loinc.answerlist.version=Beta.1 diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java index f8e15870a2f..d34393b7b61 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java @@ -521,7 +521,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { myTerminologyDeferredStorageSvc.saveDeferred(); myTerminologyDeferredStorageSvc.saveDeferred(); - IValidationSupport.LookupCodeResult lookupResults = myCodeSystemDao.lookupCode(new StringType("childAA"), new StringType(URL_MY_CODE_SYSTEM),null, mySrd); + IValidationSupport.LookupCodeResult lookupResults = myCodeSystemDao.lookupCode(new StringType("childAA"), new StringType(URL_MY_CODE_SYSTEM), null,null, mySrd); assertEquals(true, lookupResults.isFound()); ValueSet vs = new ValueSet(); @@ -711,7 +711,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { StringType code = new StringType("ParentA"); StringType system = new StringType("http://snomed.info/sct"); - IValidationSupport.LookupCodeResult outcome = myCodeSystemDao.lookupCode(code, system, null, mySrd); + IValidationSupport.LookupCodeResult outcome = myCodeSystemDao.lookupCode(code, system, null,null, mySrd); assertEquals(true, outcome.isFound()); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java index b73ced860d1..63b88299e1b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java @@ -828,7 +828,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { StringType code = new StringType("ParentA"); StringType system = new StringType("http://snomed.info/sct"); - IValidationSupport.LookupCodeResult outcome = myCodeSystemDao.lookupCode(code, system, null, mySrd); + IValidationSupport.LookupCodeResult outcome = myCodeSystemDao.lookupCode(code, system, null,null, mySrd); assertEquals(true, outcome.isFound()); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java index 7d6767f1aa4..6196d2d226f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java @@ -16,6 +16,7 @@ import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import ca.uhn.fhir.util.ParametersUtil; import ca.uhn.fhir.util.UrlUtil; import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; @@ -415,6 +416,51 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { } + @Test + public void testExpandByValueSetWithFilter() throws IOException { + loadAndPersistCodeSystem(); + + ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); + + Parameters respParam = myClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "valueSet", toExpand) + .andParameter("filter", new StringType("blood")) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, stringContainsInOrder( + "", + "")); + + } + + @Test + public void testExpandByUrlWithFilter() throws Exception { + loadAndPersistCodeSystemAndValueSet(); + + Parameters respParam = myClient + .operation() + .onType(ValueSet.class) + .named("expand") + .withParameter(Parameters.class, "url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) + .andParameter("filter", new StringType("first")) + .execute(); + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); + ourLog.info(resp); + assertThat(resp, stringContainsInOrder( + "", + "")); + + } + + @Test public void testExpandByValueSetWithPreExpansion() throws IOException { myDaoConfig.setPreExpandValueSets(true); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcTest.java index 8c6f1fc8805..cd7ddf2265f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcTest.java @@ -13,7 +13,7 @@ public class TermCodeSystemStorageSvcTest extends BaseJpaR4Test { public static final String URL_MY_CODE_SYSTEM = "http://example.com/my_code_system"; @Test - public void testStoreNewCodeSystemVersionForExistingCodeSystem() { + public void testStoreNewCodeSystemVersionForExistingCodeSystemNoVersionId() { CodeSystem upload = createCodeSystemWithMoreThan100Concepts(); // Create CodeSystem resource @@ -34,15 +34,38 @@ public class TermCodeSystemStorageSvcTest extends BaseJpaR4Test { assertEquals(125, myTermConceptDao.count()); } + @Test + public void testStoreNewCodeSystemVersionForExistingCodeSystemVersionId() { + CodeSystem upload = createCodeSystemWithMoreThan100Concepts(); + upload.setVersion("1"); + + // Create CodeSystem resource + ResourceTable codeSystemResourceEntity = (ResourceTable) myCodeSystemDao.create(upload, mySrd).getEntity(); + + // Update the CodeSystem resource + runInTransaction(() -> myTermCodeSystemStorageSvc.storeNewCodeSystemVersionIfNeeded(upload, codeSystemResourceEntity)); + + /* + Because there are more than 100 concepts in the code system, the first 100 will be persisted immediately and + the remaining 25 concepts will be queued up for "deferred save". + + As the CodeSystem was persisted twice, the extra 25 term concepts will be queued twice, each with a different + CodeSystem version PID. Only one set of the term concepts should be persisted (i.e. 125 term concepts in total). + */ + myTerminologyDeferredStorageSvc.setProcessDeferred(true); + myTerminologyDeferredStorageSvc.saveDeferred(); + assertEquals(125, myTermConceptDao.count()); + } + private CodeSystem createCodeSystemWithMoreThan100Concepts() { CodeSystem codeSystem = new CodeSystem(); codeSystem.setUrl(URL_MY_CODE_SYSTEM); - codeSystem.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT); for (int i = 0; i < 125; i++) { codeSystem.addConcept(new CodeSystem.ConceptDefinitionComponent(new CodeType("codeA " + i))); } + codeSystem.setContent(CodeSystem.CodeSystemContentMode.COMPLETE); return codeSystem; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcLoincTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcLoincTest.java index c41a29eddb6..df83c976ec7 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcLoincTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcLoincTest.java @@ -17,12 +17,10 @@ import ca.uhn.fhir.jpa.term.loinc.LoincTop2000LabResultsUsHandler; import ca.uhn.fhir.jpa.term.loinc.LoincUniversalOrderSetHandler; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; -import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.ValueSet; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -342,6 +340,28 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { assertEquals("13006-2", vs.getCompose().getInclude().get(0).getConcept().get(1).getCode()); } + @Test + public void testLoadLoincMultipleVersions() throws IOException { + + // Load LOINC marked as version 2.67 + addLoincMandatoryFilesWithPropertiesFileToZip(myFiles, "v267_loincupload.properties"); + mySvc.loadLoinc(myFiles.getFiles(), mySrd); + + verify(myTermCodeSystemStorageSvc, times(1)).storeNewCodeSystemVersion(mySystemCaptor.capture(), myCsvCaptor.capture(), any(RequestDetails.class), myValueSetsCaptor.capture(), myConceptMapCaptor.capture()); + CodeSystem loincCS = mySystemCaptor.getValue(); + assertEquals("2.67", loincCS.getVersion()); + + // Load LOINC marked as version 2.68 + myFiles = new ZipCollectionBuilder(); + addLoincMandatoryFilesWithPropertiesFileToZip(myFiles, "v268_loincupload.properties"); + mySvc.loadLoinc(myFiles.getFiles(), mySrd); + + verify(myTermCodeSystemStorageSvc, times(2)).storeNewCodeSystemVersion(mySystemCaptor.capture(), myCsvCaptor.capture(), any(RequestDetails.class), myValueSetsCaptor.capture(), myConceptMapCaptor.capture()); + loincCS = mySystemCaptor.getValue(); + assertEquals("2.68", loincCS.getVersion()); + + } + @Test @Disabled public void testLoadLoincMandatoryFilesOnly() throws IOException { @@ -382,7 +402,11 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { } public static void addLoincMandatoryFilesToZip(ZipCollectionBuilder theFiles) throws IOException { - theFiles.addFileZip("/loinc/", LOINC_UPLOAD_PROPERTIES_FILE.getCode()); + addLoincMandatoryFilesWithPropertiesFileToZip(theFiles, LOINC_UPLOAD_PROPERTIES_FILE.getCode()); + } + + public static void addLoincMandatoryFilesWithPropertiesFileToZip(ZipCollectionBuilder theFiles, String thePropertiesFile) throws IOException { + theFiles.addFileZip("/loinc/", thePropertiesFile); theFiles.addFileZip("/loinc/", LOINC_GROUP_FILE_DEFAULT.getCode()); theFiles.addFileZip("/loinc/", LOINC_GROUP_TERMS_FILE_DEFAULT.getCode()); theFiles.addFileZip("/loinc/", LOINC_PARENT_GROUP_FILE_DEFAULT.getCode()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcDeltaR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcDeltaR4Test.java index 265e2064761..db601c7a330 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcDeltaR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcDeltaR4Test.java @@ -184,7 +184,7 @@ public class TerminologySvcDeltaR4Test extends BaseJpaR4Test { assertEquals(2, outcome.getUpdatedConceptCount()); runInTransaction(() -> { - TermConcept concept = myTermSvc.findCode("http://foo/cs", "ChildAA").orElseThrow(() -> new IllegalStateException()); + TermConcept concept = myTermSvc.findCode("http://foo/cs", "ChildAA", null).orElseThrow(() -> new IllegalStateException()); assertEquals(2, concept.getParents().size()); assertThat(concept.getParentPidsAsString(), matchesPattern("^[0-9]+ [0-9]+$")); }); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java index d0ff1cebb37..51b817bbb34 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java @@ -4,11 +4,15 @@ import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.jpa.api.model.TranslationRequest; +import ca.uhn.fhir.jpa.entity.TermCodeSystem; +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; +import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptMap; import ca.uhn.fhir.jpa.entity.TermConceptMapGroup; import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement; import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget; import ca.uhn.fhir.jpa.entity.TermValueSet; +import ca.uhn.fhir.model.dstu2.valueset.ContentTypeEnum; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.CanonicalType; @@ -30,9 +34,14 @@ import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -1880,4 +1889,109 @@ public class TerminologySvcImplR4Test extends BaseTermR4Test { assertTrue(result.isOk()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); } + + @Test + public void testCreateCodeSystemTwoVersions() { + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl(CS_URL); + codeSystem.setContent(CodeSystem.CodeSystemContentMode.COMPLETE); + codeSystem + .addConcept().setCode("A").setDisplay("Code A"); + codeSystem + .addConcept().setCode("B").setDisplay("Code A"); + + codeSystem.setVersion("1"); + + IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified(); + + Set codes = myTermSvc.findCodesBelow(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "A"); + assertThat(toCodes(codes), containsInAnyOrder("A")); + + codes = myTermSvc.findCodesBelow(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "B"); + assertThat(toCodes(codes), containsInAnyOrder("B")); + + runInTransaction(() -> { + List termCodeSystemVersions = myTermCodeSystemVersionDao.findAll(); + assertEquals(termCodeSystemVersions.size(), 1); + TermCodeSystemVersion termCodeSystemVersion_1 = termCodeSystemVersions.get(0); + assertEquals(termCodeSystemVersion_1.getConcepts().size(), 2); + Set termConcepts = new HashSet<>(termCodeSystemVersion_1.getConcepts()); + assertThat(toCodes(termConcepts), containsInAnyOrder("A", "B")); + + TermCodeSystem termCodeSystem = myTermCodeSystemDao.findByResourcePid(id.getIdPartAsLong()); + assertEquals("1", termCodeSystem.getCurrentVersion().getCodeSystemVersionId()); + + }); + + codeSystem.setVersion("2"); + codeSystem + .addConcept().setCode("C").setDisplay("Code C"); + + myCodeSystemDao.update(codeSystem, mySrd).getId().toUnqualified(); + codes = myTermSvc.findCodesBelow(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "C"); + assertThat(toCodes(codes), containsInAnyOrder("C")); + + runInTransaction(() -> { + List termCodeSystemVersions_updated = myTermCodeSystemVersionDao.findAll(); + assertEquals(termCodeSystemVersions_updated.size(), 2); + TermCodeSystemVersion termCodeSystemVersion_2 = termCodeSystemVersions_updated.get(1); + assertEquals(termCodeSystemVersion_2.getConcepts().size(), 3); + Set termConcepts_updated = new HashSet<>(termCodeSystemVersion_2.getConcepts()); + assertThat(toCodes(termConcepts_updated), containsInAnyOrder("A", "B", "C")); + + TermCodeSystem termCodeSystem = myTermCodeSystemDao.findByResourcePid(id.getIdPartAsLong()); + assertEquals("2", termCodeSystem.getCurrentVersion().getCodeSystemVersionId()); + }); + } + + @Test + public void testUpdateCodeSystemContentModeNotPresent() { + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl(CS_URL); + codeSystem.setContent(CodeSystem.CodeSystemContentMode.COMPLETE); + codeSystem + .addConcept().setCode("A").setDisplay("Code A"); + codeSystem + .addConcept().setCode("B").setDisplay("Code A"); + + codeSystem.setVersion("1"); + + IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified(); + + Set codes = myTermSvc.findCodesBelow(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "A"); + assertThat(toCodes(codes), containsInAnyOrder("A")); + + codes = myTermSvc.findCodesBelow(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "B"); + assertThat(toCodes(codes), containsInAnyOrder("B")); + + runInTransaction(() -> { + List termCodeSystemVersions = myTermCodeSystemVersionDao.findAll(); + assertEquals(termCodeSystemVersions.size(), 1); + TermCodeSystemVersion termCodeSystemVersion_1 = termCodeSystemVersions.get(0); + assertEquals(termCodeSystemVersion_1.getConcepts().size(), 2); + Set termConcepts = new HashSet<>(termCodeSystemVersion_1.getConcepts()); + assertThat(toCodes(termConcepts), containsInAnyOrder("A", "B")); + + TermCodeSystem termCodeSystem = myTermCodeSystemDao.findByResourcePid(id.getIdPartAsLong()); + assertEquals("1", termCodeSystem.getCurrentVersion().getCodeSystemVersionId()); + + }); + + // Remove concepts and changed ContentMode to NOTPRESENT + codeSystem.setConcept(new ArrayList<>()); + codeSystem.setContent((CodeSystem.CodeSystemContentMode.NOTPRESENT)); + + myCodeSystemDao.update(codeSystem, mySrd).getId().toUnqualified(); + runInTransaction(() -> { + List termCodeSystemVersions_updated = myTermCodeSystemVersionDao.findAll(); + assertEquals(termCodeSystemVersions_updated.size(), 1); + TermCodeSystemVersion termCodeSystemVersion_2 = termCodeSystemVersions_updated.get(0); + assertEquals(termCodeSystemVersion_2.getConcepts().size(), 2); + Set termConcepts_updated = new HashSet<>(termCodeSystemVersion_2.getConcepts()); + assertThat(toCodes(termConcepts_updated), containsInAnyOrder("A", "B")); + + TermCodeSystem termCodeSystem = myTermCodeSystemDao.findByResourcePid(id.getIdPartAsLong()); + assertEquals("1", termCodeSystem.getCurrentVersion().getCodeSystemVersionId()); + }); + } } diff --git a/hapi-fhir-jpaserver-base/src/test/resources/loinc/v267_loincupload.properties b/hapi-fhir-jpaserver-base/src/test/resources/loinc/v267_loincupload.properties new file mode 100644 index 00000000000..f9a049fdf06 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/resources/loinc/v267_loincupload.properties @@ -0,0 +1,87 @@ +################# +### MANDATORY ### +################# + +# Answer lists (ValueSets of potential answers/values for LOINC "questions") +## File must be present +loinc.answerlist.file=AccessoryFiles/AnswerFile/AnswerList.csv +# Answer list links (connects LOINC observation codes to answer list codes) +## File must be present +loinc.answerlist.link.file=AccessoryFiles/AnswerFile/LoincAnswerListLink.csv + +# Document ontology +## File must be present +loinc.document.ontology.file=AccessoryFiles/DocumentOntology/DocumentOntology.csv + +# LOINC codes +## File must be present +loinc.file=LoincTable/Loinc.csv + +# LOINC hierarchy +## File must be present +loinc.hierarchy.file=AccessoryFiles/MultiAxialHierarchy/MultiAxialHierarchy.csv + +# IEEE medical device codes +## File must be present +loinc.ieee.medical.device.code.mapping.table.file=AccessoryFiles/LoincIeeeMedicalDeviceCodeMappingTable/LoincIeeeMedicalDeviceCodeMappingTable.csv + +# Imaging document codes +## File must be present +loinc.imaging.document.codes.file=AccessoryFiles/ImagingDocuments/ImagingDocumentCodes.csv + +# Part +## File must be present +loinc.part.file=AccessoryFiles/PartFile/Part.csv + +# Part link +## File must be present +loinc.part.link.primary.file=AccessoryFiles/PartFile/LoincPartLink_Primary.csv +loinc.part.link.supplementary.file=AccessoryFiles/PartFile/LoincPartLink_Supplementary.csv + +# Part related code mapping +## File must be present +loinc.part.related.code.mapping.file=AccessoryFiles/PartFile/PartRelatedCodeMapping.csv + +# RSNA playbook +## File must be present +loinc.rsna.playbook.file=AccessoryFiles/LoincRsnaRadiologyPlaybook/LoincRsnaRadiologyPlaybook.csv + +# Top 2000 codes - SI +## File must be present +loinc.top2000.common.lab.results.si.file=AccessoryFiles/Top2000Results/SI/Top2000CommonLabResultsSi.csv +# Top 2000 codes - US +## File must be present +loinc.top2000.common.lab.results.us.file=AccessoryFiles/Top2000Results/US/Top2000CommonLabResultsUs.csv + +# Universal lab order ValueSet +## File must be present +loinc.universal.lab.order.valueset.file=AccessoryFiles/LoincUniversalLabOrdersValueSet/LoincUniversalLabOrdersValueSet.csv + +################ +### OPTIONAL ### +################ + +# This is the version identifier for the LOINC code system +## Key may be omitted if only a single version of LOINC is being kept. +loinc.codesystem.version=2.67 + +# This is the version identifier for the answer list file +## Key may be omitted +loinc.answerlist.version=Beta.1 + +# This is the version identifier for uploaded ConceptMap resources +## Key may be omitted +loinc.conceptmap.version=Beta.1 + +# Group +## Default value if key not provided: AccessoryFiles/GroupFile/Group.csv +## File may be omitted +loinc.group.file=AccessoryFiles/GroupFile/Group.csv +# Group terms +## Default value if key not provided: AccessoryFiles/GroupFile/GroupLoincTerms.csv +## File may be omitted +loinc.group.terms.file=AccessoryFiles/GroupFile/GroupLoincTerms.csv +# Parent group +## Default value if key not provided: AccessoryFiles/GroupFile/ParentGroup.csv +## File may be omitted +loinc.parent.group.file=AccessoryFiles/GroupFile/ParentGroup.csv diff --git a/hapi-fhir-jpaserver-base/src/test/resources/loinc/v268_loincupload.properties b/hapi-fhir-jpaserver-base/src/test/resources/loinc/v268_loincupload.properties new file mode 100644 index 00000000000..7f4854293de --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/resources/loinc/v268_loincupload.properties @@ -0,0 +1,87 @@ +################# +### MANDATORY ### +################# + +# This is the version identifier for the LOINC code system +## Key may be omitted if only a single version of LOINC is being kept. +loinc.codesystem.version=2.68 + +# Answer lists (ValueSets of potential answers/values for LOINC "questions") +## File must be present +loinc.answerlist.file=AccessoryFiles/AnswerFile/AnswerList.csv +# Answer list links (connects LOINC observation codes to answer list codes) +## File must be present +loinc.answerlist.link.file=AccessoryFiles/AnswerFile/LoincAnswerListLink.csv + +# Document ontology +## File must be present +loinc.document.ontology.file=AccessoryFiles/DocumentOntology/DocumentOntology.csv + +# LOINC codes +## File must be present +loinc.file=LoincTable/Loinc.csv + +# LOINC hierarchy +## File must be present +loinc.hierarchy.file=AccessoryFiles/MultiAxialHierarchy/MultiAxialHierarchy.csv + +# IEEE medical device codes +## File must be present +loinc.ieee.medical.device.code.mapping.table.file=AccessoryFiles/LoincIeeeMedicalDeviceCodeMappingTable/LoincIeeeMedicalDeviceCodeMappingTable.csv + +# Imaging document codes +## File must be present +loinc.imaging.document.codes.file=AccessoryFiles/ImagingDocuments/ImagingDocumentCodes.csv + +# Part +## File must be present +loinc.part.file=AccessoryFiles/PartFile/Part.csv + +# Part link +## File must be present +loinc.part.link.primary.file=AccessoryFiles/PartFile/LoincPartLink_Primary.csv +loinc.part.link.supplementary.file=AccessoryFiles/PartFile/LoincPartLink_Supplementary.csv + +# Part related code mapping +## File must be present +loinc.part.related.code.mapping.file=AccessoryFiles/PartFile/PartRelatedCodeMapping.csv + +# RSNA playbook +## File must be present +loinc.rsna.playbook.file=AccessoryFiles/LoincRsnaRadiologyPlaybook/LoincRsnaRadiologyPlaybook.csv + +# Top 2000 codes - SI +## File must be present +loinc.top2000.common.lab.results.si.file=AccessoryFiles/Top2000Results/SI/Top2000CommonLabResultsSi.csv +# Top 2000 codes - US +## File must be present +loinc.top2000.common.lab.results.us.file=AccessoryFiles/Top2000Results/US/Top2000CommonLabResultsUs.csv + +# Universal lab order ValueSet +## File must be present +loinc.universal.lab.order.valueset.file=AccessoryFiles/LoincUniversalLabOrdersValueSet/LoincUniversalLabOrdersValueSet.csv + +################ +### OPTIONAL ### +################ + +# This is the version identifier for the answer list file +## Key may be omitted +loinc.answerlist.version=Beta.1 + +# This is the version identifier for uploaded ConceptMap resources +## Key may be omitted +loinc.conceptmap.version=Beta.1 + +# Group +## Default value if key not provided: AccessoryFiles/GroupFile/Group.csv +## File may be omitted +loinc.group.file=AccessoryFiles/GroupFile/Group.csv +# Group terms +## Default value if key not provided: AccessoryFiles/GroupFile/GroupLoincTerms.csv +## File may be omitted +loinc.group.terms.file=AccessoryFiles/GroupFile/GroupLoincTerms.csv +# Parent group +## Default value if key not provided: AccessoryFiles/GroupFile/ParentGroup.csv +## File may be omitted +loinc.parent.group.file=AccessoryFiles/GroupFile/ParentGroup.csv diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/BaseValidationSupportWrapper.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/BaseValidationSupportWrapper.java index cc04a447d10..3c6c10f9fb3 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/BaseValidationSupportWrapper.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/BaseValidationSupportWrapper.java @@ -58,6 +58,11 @@ public class BaseValidationSupportWrapper extends BaseValidationSupport { return myWrap.validateCode(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, theValueSetUrl); } + @Override + public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theVersion) { + return myWrap.lookupCode(theValidationSupportContext, theSystem, theCode, theVersion); + } + @Override public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { return myWrap.lookupCode(theValidationSupportContext, theSystem, theCode); diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java index 829b9977ad7..53c15009f11 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java @@ -83,6 +83,12 @@ public class CachingValidationSupport extends BaseValidationSupportWrapper imple return loadFromCache(myValidateCodeCache, key, t -> super.validateCode(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, theValueSetUrl)); } + @Override + public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theVersion) { + String key = "lookupCode " + theSystem + " " + theCode + " " + theVersion; + return loadFromCache(myLookupCodeCache, key, t -> super.lookupCode(theValidationSupportContext, theSystem, theCode, theVersion)); + } + @Override public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { String key = "lookupCode " + theSystem + " " + theCode; diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java index b447c39e160..51dfe5dd86b 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java @@ -145,6 +145,12 @@ public class CommonCodeSystemsTerminologyService implements IValidationSupport { } + @Override + public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theVersion) { + // For now Common Code Systems will not be versioned. + return lookupCode(theValidationSupportContext, theSystem, theCode); + } + @Override public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java index 3fca6d034ac..dc1c3576c14 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java @@ -308,6 +308,12 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu .setMessage(message); } + @Override + public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theVersion) { + // TODO: Add support for validating versioned codes as well. + return lookupCode(theValidationSupportContext, theSystem, theCode); + } + @Override public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { return validateCode(theValidationSupportContext, new ConceptValidationOptions(), theSystem, theCode, null, null).asLookupCodeResult(theSystem, theCode); @@ -470,6 +476,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu List nextCodeList = new ArrayList<>(); String system = nextInclude.getSystem(); + String systemVersion = nextInclude.getVersion(); if (isNotBlank(system)) { if (theWantSystem != null && !theWantSystem.equals(system)) { @@ -492,7 +499,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu if (theWantCode != null) { if (theValidationSupportContext.getRootValidationSupport().isCodeSystemSupported(theValidationSupportContext, system)) { - LookupCodeResult lookup = theValidationSupportContext.getRootValidationSupport().lookupCode(theValidationSupportContext, system, theWantCode); + LookupCodeResult lookup = theValidationSupportContext.getRootValidationSupport().lookupCode(theValidationSupportContext, system, theWantCode, systemVersion); if (lookup != null && lookup.isFound()) { CodeSystem.ConceptDefinitionComponent conceptDefinition = new CodeSystem.ConceptDefinitionComponent() .addConcept() diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.java index 841038fd4b7..ac7da03cd07 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.java @@ -257,6 +257,16 @@ public class ValidationSupportChain implements IValidationSupport { return null; } + @Override + public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theVersion) { + for (IValidationSupport next : myChain) { + if (next.isCodeSystemSupported(theValidationSupportContext, theSystem)) { + return next.lookupCode(theValidationSupportContext, theSystem, theCode, theVersion); + } + } + return null; + } + @Override public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { for (IValidationSupport next : myChain) { From 4db00fa604579abe1b4b64695c6ffda6a1da25c4 Mon Sep 17 00:00:00 2001 From: ianmarshall Date: Mon, 17 Aug 2020 18:14:37 -0400 Subject: [PATCH 2/8] Fix broken test and cleanup. --- .../ca/uhn/fhir/jpa/api/config/DaoConfig.java | 35 ------------------- .../jpa/dao/FhirResourceDaoValueSetDstu2.java | 3 -- .../dstu3/FhirResourceDaoCodeSystemDstu3.java | 5 ++- .../dao/r4/FhirResourceDaoCodeSystemR4.java | 5 ++- .../dao/r5/FhirResourceDaoCodeSystemR5.java | 5 ++- .../uhn/fhir/jpa/term/TermLoaderSvcImpl.java | 2 +- .../FhirInstanceValidatorR4Test.java | 2 +- 7 files changed, 8 insertions(+), 49 deletions(-) diff --git a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/config/DaoConfig.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/config/DaoConfig.java index 85b6bb6b8ca..98f49f6ff2d 100644 --- a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/config/DaoConfig.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/config/DaoConfig.java @@ -227,11 +227,6 @@ public class DaoConfig { */ private boolean myPreloadBlobFromInputStream = false; - /** - * @since 5.1.0 - */ - private boolean myMultipleCodeSystemVersionsEnabled = false; - /** * Constructor */ @@ -2131,34 +2126,4 @@ public class DaoConfig { myPreloadBlobFromInputStream = thePreloadBlobFromInputStream; } - /** - *

- * This determines whether multiple code system versions will be enabled. If not enabled, existing code systems will be - * deleted when a new version is uploaded. - *

- *

- * The default value for this setting is {@code false}. - *

- * - * @since 5.1.0 - */ - public boolean isMultipleCodeSystemVersionsEnabled() { - return myMultipleCodeSystemVersionsEnabled; - } - - /** - *

- * This determines whether multiple code system versions will be enabled. If not enabled, existing code systems will be - * deleted when a new version is uploaded. - *

- *

- * The default value for this setting is {@code false}. - *

- * - * @since 5.1.0 - */ - public void setMultipleCodeSystemVersionsEnabled(Boolean theMultipleCodeSystemVersionsEnabled) { - myMultipleCodeSystemVersionsEnabled = theMultipleCodeSystemVersionsEnabled; - } - } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2.java index c43d3e3d9c2..12cbd8fa2f5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2.java @@ -43,12 +43,10 @@ import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.UriParam; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; -import org.apache.commons.codec.binary.StringUtils; import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport; import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; -import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -236,7 +234,6 @@ public class FhirResourceDaoValueSetDstu2 extends BaseHapiFhirResourceDao theCode, IPrimitiveType theSystem, CodingDt theCoding, IPrimitiveType theVersion, RequestDetails theRequestDetails) { throw new UnsupportedOperationException(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java index 6d2cd77aa99..6a680ed97b8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java @@ -44,7 +44,6 @@ import org.hl7.fhir.dstu3.model.Coding; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; -import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import javax.annotation.Nonnull; @@ -87,7 +86,7 @@ public class FhirResourceDaoCodeSystemDstu3 extends BaseHapiFhirResourceDao theCode, IPrimitiveType theSystem, Coding theCoding, RequestDetails theRequestDetails) { return lookupCode(theCode, theSystem, theCoding, null, theRequestDetails); @@ -121,7 +120,7 @@ public class FhirResourceDaoCodeSystemDstu3 extends BaseHapiFhirResourceDao theCode, IPrimitiveType theSystem, Coding theCoding, RequestDetails theRequestDetails) { return lookupCode(theCode, theSystem, theCoding, null, theRequestDetails); @@ -116,7 +115,7 @@ public class FhirResourceDaoCodeSystemR4 extends BaseHapiFhirResourceDao theCode, IPrimitiveType theSystem, Coding theCoding, RequestDetails theRequestDetails) { return lookupCode(theCode, theSystem, theCoding, null, theRequestDetails); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermLoaderSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermLoaderSvcImpl.java index 3fe945458a5..0068ba1ee11 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermLoaderSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermLoaderSvcImpl.java @@ -504,7 +504,7 @@ public class TermLoaderSvcImpl implements ITermLoaderSvc { retVal.setId("loinc-all"); retVal.setUrl("http://loinc.org/vs"); retVal.setVersion("1.0.0"); - retVal.setName("All LOINC codes"); + retVal.setName("Ø"); retVal.setStatus(Enumerations.PublicationStatus.ACTIVE); retVal.setDate(new Date()); retVal.setPublisher("Regenstrief Institute, Inc."); diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java index 48d5cb31eb7..d5055423b05 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java @@ -254,7 +254,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest { return retVal; } }); - when(mockSupport.lookupCode(any(), any(), any())).thenAnswer(t -> { + when(mockSupport.lookupCode(any(), any(), any(), any())).thenAnswer(t -> { String system = t.getArgument(1, String.class); String code = t.getArgument(2, String.class); if (myValidConcepts.contains(system + "___" + code)) { From c1dcf0442c409b521bafd4664771ca11383dcb7f Mon Sep 17 00:00:00 2001 From: ianmarshall Date: Mon, 17 Aug 2020 21:52:16 -0400 Subject: [PATCH 3/8] Fix broken tests. --- .../provider/dstu3/BaseJpaResourceProviderCodeSystemDstu3.java | 3 ++- .../jpa/provider/r4/BaseJpaResourceProviderCodeSystemR4.java | 3 ++- .../jpa/provider/r5/BaseJpaResourceProviderCodeSystemR5.java | 3 ++- .../src/main/java/ca/uhn/fhir/jpa/term/TermLoaderSvcImpl.java | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderCodeSystemDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderCodeSystemDstu3.java index a6d061c3e06..7cc793e5371 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderCodeSystemDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderCodeSystemDstu3.java @@ -85,13 +85,14 @@ public class BaseJpaResourceProviderCodeSystemDstu3 extends JpaResourceProviderD @OperationParam(name = "system", min = 0, max = 1) UriType theSystem, @OperationParam(name = "codingA", min = 0, max = 1) Coding theCodingA, @OperationParam(name = "codingB", min = 0, max = 1) Coding theCodingB, + @OperationParam(name="version", min=0, max=1) org.hl7.fhir.r4.model.StringType theVersion, RequestDetails theRequestDetails ) { startRequest(theServletRequest); try { IFhirResourceDaoCodeSystem dao = (IFhirResourceDaoCodeSystem) getDao(); - IFhirResourceDaoCodeSystem.SubsumesResult result = dao.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theRequestDetails); + IFhirResourceDaoCodeSystem.SubsumesResult result = dao.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theVersion, theRequestDetails); return (Parameters) result.toParameters(theRequestDetails.getFhirContext()); } finally { endRequest(theServletRequest); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderCodeSystemR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderCodeSystemR4.java index dec2d1adbcc..9b78c83991b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderCodeSystemR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderCodeSystemR4.java @@ -85,13 +85,14 @@ public class BaseJpaResourceProviderCodeSystemR4 extends JpaResourceProviderR4 dao = (IFhirResourceDaoCodeSystem) getDao(); - IFhirResourceDaoCodeSystem.SubsumesResult result = dao.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theRequestDetails); + IFhirResourceDaoCodeSystem.SubsumesResult result = dao.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theVersion, theRequestDetails); return (Parameters) result.toParameters(theRequestDetails.getFhirContext()); } finally { endRequest(theServletRequest); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCodeSystemR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCodeSystemR5.java index 45d80b0228f..bcd7445766f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCodeSystemR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCodeSystemR5.java @@ -85,13 +85,14 @@ public class BaseJpaResourceProviderCodeSystemR5 extends JpaResourceProviderR5 dao = (IFhirResourceDaoCodeSystem) getDao(); - IFhirResourceDaoCodeSystem.SubsumesResult result = dao.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theRequestDetails); + IFhirResourceDaoCodeSystem.SubsumesResult result = dao.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theVersion, theRequestDetails); return (Parameters) result.toParameters(theRequestDetails.getFhirContext()); } finally { endRequest(theServletRequest); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermLoaderSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermLoaderSvcImpl.java index 0068ba1ee11..3fe945458a5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermLoaderSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermLoaderSvcImpl.java @@ -504,7 +504,7 @@ public class TermLoaderSvcImpl implements ITermLoaderSvc { retVal.setId("loinc-all"); retVal.setUrl("http://loinc.org/vs"); retVal.setVersion("1.0.0"); - retVal.setName("Ø"); + retVal.setName("All LOINC codes"); retVal.setStatus(Enumerations.PublicationStatus.ACTIVE); retVal.setDate(new Date()); retVal.setPublisher("Regenstrief Institute, Inc."); From dc786eaff920b70c62500d30fb561e287c9e8bba Mon Sep 17 00:00:00 2001 From: ianmarshall Date: Fri, 21 Aug 2020 10:16:10 -0400 Subject: [PATCH 4/8] Refine model for storing multiple code versions. --- .../ca/uhn/fhir/i18n/hapi-messages.properties | 1 + .../term/TermCodeSystemStorageSvcImpl.java | 41 ++++++++++++++----- .../jpa/term/TerminologySvcImplR4Test.java | 2 +- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties index aecaba0f495..c8b2f25d067 100644 --- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties @@ -141,6 +141,7 @@ ca.uhn.fhir.jpa.binstore.BinaryAccessProvider.unknownType=Content in resource of ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateCodeSystemUrl=Can not create multiple CodeSystem resources with CodeSystem.url "{0}", already have one with resource ID: {1} +ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateCodeSystemUrlAndVersion=Can not create multiple CodeSystem resources with CodeSystem.url "{0}" and CodeSystem.version "{1}", already have one with resource ID: {2} ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateConceptMapUrl=Can not create multiple ConceptMap resources with ConceptMap.url "{0}", already have one with resource ID: {1} ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateValueSetUrl=Can not create multiple ValueSet resources with ValueSet.url "{0}", already have one with resource ID: {1} ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.expansionTooLarge=Expansion of ValueSet produced too many codes (maximum {0}) - Operation aborted! diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java index 13683722d6b..486d1da3fbb 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java @@ -294,7 +294,8 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { if (termCodeSystem != null) { TermCodeSystemVersion codeSystemVersion = getExistingTermCodeSystemVersion(termCodeSystem.getPid(), theCodeSystem.getVersion()); if (codeSystemVersion != null) { - getOrCreateTermCodeSystem(codeSystemResourcePid, theCodeSystem.getUrl(), theCodeSystem.getUrl(), theResourceEntity); + TermCodeSystem myCodeSystemEntity = getOrCreateTermCodeSystem(codeSystemResourcePid, theCodeSystem.getUrl(), theCodeSystem.getUrl(), theResourceEntity); + validateCodeSystemVersionUpdate(myCodeSystemEntity,theCodeSystem.getUrl(), theCodeSystem.getVersion(), theResourceEntity); return; } } @@ -348,6 +349,12 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { existing = getExistingTermCodeSystemVersion(existingCodeSystem.getPid(), theSystemVersionId); } + /* + * Get CodeSystem and validate CodeSystemVersion + */ + TermCodeSystem codeSystem = getOrCreateTermCodeSystem(theCodeSystemResourcePid, theSystemUri, theSystemName, theCodeSystemResourceTable); + validateCodeSystemVersionUpdate(codeSystem, theSystemUri, theSystemVersionId, theCodeSystemResourceTable); + /* * Delete version being replaced. */ @@ -363,8 +370,6 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { * Do the upload */ - TermCodeSystem codeSystem = getOrCreateTermCodeSystem(theCodeSystemResourcePid, theSystemUri, theSystemName, theCodeSystemResourceTable); - theCodeSystemVersion.setCodeSystem(codeSystem); theCodeSystemVersion.setCodeSystemDisplayName(theSystemName); @@ -470,10 +475,9 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { ourLog.info(" * Removing code system version {} as current version of code system {}", theCodeSystemVersionPid, codeSystem.getPid()); codeSystem.setCurrentVersion(null); myCodeSystemDao.save(codeSystem); + myCodeSystemDao.flush(); } - myConceptDao.flush(); - ourLog.info(" * Deleting code system version"); myCodeSystemVersionDao.delete(theCodeSystemVersionPid); myCodeSystemVersionDao.flush(); @@ -677,12 +681,6 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { codeSystem = new TermCodeSystem(); } codeSystem.setResource(theCodeSystemResourceTable); - } else { - if (!ObjectUtil.equals(codeSystem.getResource().getId(), theCodeSystemResourceTable.getId())) { - String msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "cannotCreateDuplicateCodeSystemUrl", theSystemUri, - codeSystem.getResource().getIdDt().toUnqualifiedVersionless().getValue()); - throw new UnprocessableEntityException(msg); - } } codeSystem.setCodeSystemUri(theSystemUri); @@ -691,6 +689,27 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { return codeSystem; } + private void validateCodeSystemVersionUpdate(TermCodeSystem theCodeSystem, String theSystemUri, String theSystemVersionId, ResourceTable theCodeSystemResourceTable) { + // Check if CodeSystemVersion entity already exists. + TermCodeSystemVersion codeSystemVersionEntity; + String msg; + if (theSystemVersionId == null) { + codeSystemVersionEntity = myCodeSystemVersionDao.findByCodeSystemPidVersionIsNull(theCodeSystem.getPid()); + msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "cannotCreateDuplicateCodeSystemUrl", theSystemUri, + theCodeSystem.getResource().getIdDt().toUnqualifiedVersionless().getValue()); + } else { + codeSystemVersionEntity = myCodeSystemVersionDao.findByCodeSystemPidAndVersion(theCodeSystem.getPid(), theSystemVersionId); + msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "cannotCreateDuplicateCodeSystemUrlAndVersion", theSystemUri, + theSystemVersionId, theCodeSystem.getResource().getIdDt().toUnqualifiedVersionless().getValue()); + } + // Throw exception if the CodeSystemVersion is being duplicated. + if (codeSystemVersionEntity != null) { + if (!ObjectUtil.equals(codeSystemVersionEntity.getResource().getId(), theCodeSystemResourceTable.getId())) { + throw new UnprocessableEntityException(msg); + } + } + } + private void populateCodeSystemVersionProperties(TermCodeSystemVersion theCodeSystemVersion, CodeSystem theCodeSystemResource, ResourceTable theResourceTable) { theCodeSystemVersion.setResource(theResourceTable); theCodeSystemVersion.setCodeSystemDisplayName(theCodeSystemResource.getName()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java index 51b817bbb34..57e543a57e5 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java @@ -1927,7 +1927,7 @@ public class TerminologySvcImplR4Test extends BaseTermR4Test { codeSystem .addConcept().setCode("C").setDisplay("Code C"); - myCodeSystemDao.update(codeSystem, mySrd).getId().toUnqualified(); + myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified(); codes = myTermSvc.findCodesBelow(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "C"); assertThat(toCodes(codes), containsInAnyOrder("C")); From 749c2bc4be43b23b06c6755c6d08ab6d01bc2445 Mon Sep 17 00:00:00 2001 From: ianmarshall Date: Tue, 25 Aug 2020 12:06:07 -0400 Subject: [PATCH 5/8] Changes to enable request delete of specific Code System version. --- .../dstu3/FhirResourceDaoCodeSystemDstu3.java | 14 +- .../dao/r4/FhirResourceDaoCodeSystemR4.java | 14 +- .../dao/r5/FhirResourceDaoCodeSystemR5.java | 18 +- .../uhn/fhir/jpa/entity/TermCodeSystem.java | 2 +- .../term/TermCodeSystemStorageSvcImpl.java | 24 ++- .../jpa/term/TermDeferredStorageSvcImpl.java | 16 +- .../term/api/ITermCodeSystemStorageSvc.java | 2 + .../jpa/term/api/ITermDeferredStorageSvc.java | 3 + .../term/TermCodeSystemStorageSvcTest.java | 195 +++++++++++++++--- .../term/TerminologyLoaderSvcLoincTest.java | 11 +- .../jpa/term/TerminologySvcImplR4Test.java | 12 +- 11 files changed, 242 insertions(+), 69 deletions(-) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java index 6a680ed97b8..d10c33c105c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java @@ -25,7 +25,9 @@ import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; +import ca.uhn.fhir.jpa.dao.HapiFhirResourceDaoCodeSystemUtil; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; +import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao; import ca.uhn.fhir.jpa.entity.TermCodeSystem; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; @@ -67,6 +69,8 @@ public class FhirResourceDaoCodeSystemDstu3 extends BaseHapiFhirResourceDao myDefferedCodeSystemsDeletions = Collections.synchronizedList(new ArrayList<>()); + private List myDefferedCodeSystemVersionsDeletions = Collections.synchronizedList(new ArrayList<>()); private List myDeferredConcepts = Collections.synchronizedList(new ArrayList<>()); private List myDeferredValueSets = Collections.synchronizedList(new ArrayList<>()); private List myDeferredConceptMaps = Collections.synchronizedList(new ArrayList<>()); @@ -115,6 +117,12 @@ public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc { myDefferedCodeSystemsDeletions.add(theCodeSystem); } + @Override + @Transactional + public void deleteCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) { + myDefferedCodeSystemVersionsDeletions.add(theCodeSystemVersion); + } + @Override public void saveAllDeferred() { while (!isStorageQueueEmpty()) { @@ -266,6 +274,12 @@ public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc { } private void processDeferredCodeSystemDeletions() { + + for (TermCodeSystemVersion next : myDefferedCodeSystemVersionsDeletions) { + myCodeSystemStorageSvc.deleteCodeSystemVersion(next); + } + + myDefferedCodeSystemVersionsDeletions.clear(); for (TermCodeSystem next : myDefferedCodeSystemsDeletions) { myCodeSystemStorageSvc.deleteCodeSystem(next); } @@ -300,7 +314,7 @@ public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc { } private boolean isDeferredCodeSystemDeletions() { - return !myDefferedCodeSystemsDeletions.isEmpty(); + return !myDefferedCodeSystemsDeletions.isEmpty() || !myDefferedCodeSystemVersionsDeletions.isEmpty(); } private boolean isDeferredConcepts() { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermCodeSystemStorageSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermCodeSystemStorageSvc.java index 7718bcbab7a..ab3434aa6df 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermCodeSystemStorageSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermCodeSystemStorageSvc.java @@ -41,6 +41,8 @@ public interface ITermCodeSystemStorageSvc { void deleteCodeSystem(TermCodeSystem theCodeSystem); + void deleteCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion); + void storeNewCodeSystemVersion(ResourcePersistentId theCodeSystemResourcePid, String theSystemUri, String theSystemName, String theSystemVersionId, TermCodeSystemVersion theCodeSystemVersion, ResourceTable theCodeSystemResourceTable); /** diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermDeferredStorageSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermDeferredStorageSvc.java index 34eac05a92e..9fddda117f1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermDeferredStorageSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermDeferredStorageSvc.java @@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.term.api; */ import ca.uhn.fhir.jpa.entity.TermCodeSystem; +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; import org.hl7.fhir.r4.model.ConceptMap; @@ -54,6 +55,8 @@ public interface ITermDeferredStorageSvc { void deleteCodeSystem(TermCodeSystem theCodeSystem); + void deleteCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion); + /** * This is mostly here for unit tests - Saves any and all deferred concepts and links */ diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcTest.java index cd7ddf2265f..3845452a093 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcTest.java @@ -1,11 +1,19 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; +import ca.uhn.fhir.jpa.entity.TermCodeSystem; +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeType; import org.junit.jupiter.api.Test; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.fail; + + import static org.junit.jupiter.api.Assertions.assertEquals; public class TermCodeSystemStorageSvcTest extends BaseJpaR4Test { @@ -14,47 +22,123 @@ public class TermCodeSystemStorageSvcTest extends BaseJpaR4Test { @Test public void testStoreNewCodeSystemVersionForExistingCodeSystemNoVersionId() { - CodeSystem upload = createCodeSystemWithMoreThan100Concepts(); + CodeSystem firstUpload = createCodeSystemWithMoreThan100Concepts(); + CodeSystem duplicateUpload = createCodeSystemWithMoreThan100Concepts(); - // Create CodeSystem resource - ResourceTable codeSystemResourceEntity = (ResourceTable) myCodeSystemDao.create(upload, mySrd).getEntity(); + testCreatingAndUpdatingCodeSystemEntity(firstUpload, duplicateUpload, 125, "Can not create multiple CodeSystem resources with CodeSystem.url \"http://example.com/my_code_system\", already have one with resource ID: CodeSystem/"); - // Update the CodeSystem resource - runInTransaction(() -> myTermCodeSystemStorageSvc.storeNewCodeSystemVersionIfNeeded(upload, codeSystemResourceEntity)); + runInTransaction(() -> { + assertEquals(1, myTermCodeSystemDao.count()); + assertEquals(1, myTermCodeSystemVersionDao.count()); + TermCodeSystem myTermCodeSystem = myTermCodeSystemDao.findByCodeSystemUri(URL_MY_CODE_SYSTEM); - /* - Because there are more than 100 concepts in the code system, the first 100 will be persisted immediately and - the remaining 25 concepts will be queued up for "deferred save". - - As the CodeSystem was persisted twice, the extra 25 term concepts will be queued twice, each with a different - CodeSystem version PID. Only one set of the term concepts should be persisted (i.e. 125 term concepts in total). - */ - myTerminologyDeferredStorageSvc.setProcessDeferred(true); - myTerminologyDeferredStorageSvc.saveDeferred(); - assertEquals(125, myTermConceptDao.count()); + TermCodeSystemVersion myTermCodeSystemVersion = myTermCodeSystemVersionDao.findByCodeSystemPidVersionIsNull( myTermCodeSystem.getPid()); + assertEquals(myTermCodeSystem.getCurrentVersion().getPid(), myTermCodeSystemVersion.getPid()); + assertEquals(myTermCodeSystem.getResource().getId(), myTermCodeSystemVersion.getResource().getId()); + }); } + @Test public void testStoreNewCodeSystemVersionForExistingCodeSystemVersionId() { - CodeSystem upload = createCodeSystemWithMoreThan100Concepts(); - upload.setVersion("1"); + CodeSystem firstUpload = createCodeSystemWithMoreThan100Concepts(); + firstUpload.setVersion("1"); - // Create CodeSystem resource - ResourceTable codeSystemResourceEntity = (ResourceTable) myCodeSystemDao.create(upload, mySrd).getEntity(); + CodeSystem duplicateUpload = createCodeSystemWithMoreThan100Concepts(); + duplicateUpload.setVersion("1"); - // Update the CodeSystem resource - runInTransaction(() -> myTermCodeSystemStorageSvc.storeNewCodeSystemVersionIfNeeded(upload, codeSystemResourceEntity)); + testCreatingAndUpdatingCodeSystemEntity(firstUpload, duplicateUpload, 125,"Can not create multiple CodeSystem resources with CodeSystem.url \"http://example.com/my_code_system\" and CodeSystem.version \"1\", already have one with resource ID: CodeSystem/"); - /* - Because there are more than 100 concepts in the code system, the first 100 will be persisted immediately and - the remaining 25 concepts will be queued up for "deferred save". + runInTransaction(() -> { + assertEquals(1, myTermCodeSystemDao.count()); + assertEquals(1, myTermCodeSystemVersionDao.count()); + TermCodeSystem myTermCodeSystem = myTermCodeSystemDao.findByCodeSystemUri(URL_MY_CODE_SYSTEM); + + TermCodeSystemVersion myTermCodeSystemVersion = myTermCodeSystemVersionDao.findByCodeSystemPidAndVersion( myTermCodeSystem.getPid(), "1"); + assertEquals(myTermCodeSystem.getCurrentVersion().getPid(), myTermCodeSystemVersion.getPid()); + assertEquals(myTermCodeSystem.getResource().getId(), myTermCodeSystemVersion.getResource().getId()); + }); + + // Now add a second version + firstUpload = createCodeSystemWithMoreThan100Concepts(); + firstUpload.setVersion("2"); + + duplicateUpload = createCodeSystemWithMoreThan100Concepts(); + duplicateUpload.setVersion("2"); + + testCreatingAndUpdatingCodeSystemEntity(firstUpload, duplicateUpload, 251,"Can not create multiple CodeSystem resources with CodeSystem.url \"http://example.com/my_code_system\" and CodeSystem.version \"2\", already have one with resource ID: CodeSystem/"); + + runInTransaction(() -> { + assertEquals(1, myTermCodeSystemDao.count()); + assertEquals(2, myTermCodeSystemVersionDao.count()); + TermCodeSystem myTermCodeSystem = myTermCodeSystemDao.findByCodeSystemUri(URL_MY_CODE_SYSTEM); + + TermCodeSystemVersion mySecondTermCodeSystemVersion = myTermCodeSystemVersionDao.findByCodeSystemPidAndVersion(myTermCodeSystem.getPid(), "2"); + assertEquals(myTermCodeSystem.getCurrentVersion().getPid(), mySecondTermCodeSystemVersion.getPid()); + assertEquals(myTermCodeSystem.getResource().getId(), mySecondTermCodeSystemVersion.getResource().getId()); + }); + + } + + @Test + public void testDeleteCodeSystem() { + CodeSystem codeSystemToDelete = createSmallCodeSystem(); + + // Create CodeSystem resource and entity + ResourceTable codeSystemResourceEntity = (ResourceTable)myCodeSystemDao.create(codeSystemToDelete, mySrd).getEntity(); + validateCodeSystemUpdates(10); + + runInTransaction(() -> { + assertEquals(1, myTermCodeSystemDao.count()); + assertEquals(1, myTermCodeSystemVersionDao.count()); + }); + + // Attempt to delete + myCodeSystemDao.delete(codeSystemResourceEntity.getIdDt(), mySrd); + validateCodeSystemUpdates(0); + + runInTransaction(() -> { + assertEquals(0, myTermCodeSystemDao.count()); + assertEquals(0, myTermCodeSystemVersionDao.count()); + }); + + } + + @Test + public void testDeleteCodeSystemVersion() { + CodeSystem firstCodeSystemVersion = createSmallCodeSystem(); + firstCodeSystemVersion.setVersion("1"); + + // Create first CodeSystem resource and entity + ResourceTable codeSystemResourceEntity = (ResourceTable)myCodeSystemDao.create(firstCodeSystemVersion, mySrd).getEntity(); + validateCodeSystemUpdates(10); + + runInTransaction(() -> { + assertEquals(1, myTermCodeSystemDao.count()); + assertEquals(1, myTermCodeSystemVersionDao.count()); + }); + + CodeSystem secondCodeSystemVersion = createSmallCodeSystem(); + secondCodeSystemVersion.setVersion("2"); + + // Create CodeSystem resource and entity + myCodeSystemDao.create(secondCodeSystemVersion, mySrd); + validateCodeSystemUpdates(20); + + runInTransaction(() -> { + assertEquals(1, myTermCodeSystemDao.count()); + assertEquals(2, myTermCodeSystemVersionDao.count()); + }); + + // Attempt to delete first version + myCodeSystemDao.delete(codeSystemResourceEntity.getIdDt(), mySrd); + validateCodeSystemUpdates(10); + + runInTransaction(() -> { + assertEquals(1, myTermCodeSystemDao.count()); + assertEquals(1, myTermCodeSystemVersionDao.count()); + }); - As the CodeSystem was persisted twice, the extra 25 term concepts will be queued twice, each with a different - CodeSystem version PID. Only one set of the term concepts should be persisted (i.e. 125 term concepts in total). - */ - myTerminologyDeferredStorageSvc.setProcessDeferred(true); - myTerminologyDeferredStorageSvc.saveDeferred(); - assertEquals(125, myTermConceptDao.count()); } private CodeSystem createCodeSystemWithMoreThan100Concepts() { @@ -70,5 +154,56 @@ public class TermCodeSystemStorageSvcTest extends BaseJpaR4Test { } + private CodeSystem createSmallCodeSystem() { + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl(URL_MY_CODE_SYSTEM); + + for (int i = 0; i < 10; i++) { + codeSystem.addConcept(new CodeSystem.ConceptDefinitionComponent(new CodeType("codeA " + i))); + } + + codeSystem.setContent(CodeSystem.CodeSystemContentMode.COMPLETE); + return codeSystem; + + } + + private void testCreatingAndUpdatingCodeSystemEntity(CodeSystem theUpload, CodeSystem theDuplicate, int expectedCnt, String theDuplicateErrorBaseMsg) { + + // Create CodeSystem resource + ResourceTable codeSystemResourceEntity = (ResourceTable) myCodeSystemDao.create(theUpload, mySrd).getEntity(); + + // Create the CodeSystem and CodeSystemVersion entities + validateCodeSystemUpdates(expectedCnt); + + // Update the CodeSystem + theUpload.addConcept(new CodeSystem.ConceptDefinitionComponent(new CodeType("codeB"))); + // Update the CodeSystem and CodeSystemVersion entities + runInTransaction(() -> myTermCodeSystemStorageSvc.storeNewCodeSystemVersionIfNeeded(theUpload, codeSystemResourceEntity)); + validateCodeSystemUpdates(expectedCnt+1); + + // Try duplicating the CodeSystem + Long originalResId = codeSystemResourceEntity.getId(); + try { + myCodeSystemDao.create(theDuplicate, mySrd); + fail(); + } catch (UnprocessableEntityException e) { + assertEquals(theDuplicateErrorBaseMsg + originalResId, e.getMessage()); + } + + // Try updating code system when content mode is NOT PRESENT + theUpload.setConcept(new ArrayList<>()); + theUpload.setContent((CodeSystem.CodeSystemContentMode.NOTPRESENT)); + runInTransaction(() -> myTermCodeSystemStorageSvc.storeNewCodeSystemVersionIfNeeded(theUpload, codeSystemResourceEntity)); + validateCodeSystemUpdates(expectedCnt+1); + + } + + private void validateCodeSystemUpdates(int theExpectedConceptCount) { + myTerminologyDeferredStorageSvc.setProcessDeferred(true); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.setProcessDeferred(false); + assertEquals(theExpectedConceptCount, myTermConceptDao.count()); + + } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcLoincTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcLoincTest.java index df83c976ec7..288dcc85a29 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcLoincTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcLoincTest.java @@ -351,12 +351,21 @@ public class TerminologyLoaderSvcLoincTest extends BaseLoaderTest { CodeSystem loincCS = mySystemCaptor.getValue(); assertEquals("2.67", loincCS.getVersion()); + // Update LOINC marked as version 2.67 + myFiles = new ZipCollectionBuilder(); + addLoincMandatoryFilesWithPropertiesFileToZip(myFiles, "v267_loincupload.properties"); + mySvc.loadLoinc(myFiles.getFiles(), mySrd); + + verify(myTermCodeSystemStorageSvc, times(2)).storeNewCodeSystemVersion(mySystemCaptor.capture(), myCsvCaptor.capture(), any(RequestDetails.class), myValueSetsCaptor.capture(), myConceptMapCaptor.capture()); + loincCS = mySystemCaptor.getValue(); + assertEquals("2.67", loincCS.getVersion()); + // Load LOINC marked as version 2.68 myFiles = new ZipCollectionBuilder(); addLoincMandatoryFilesWithPropertiesFileToZip(myFiles, "v268_loincupload.properties"); mySvc.loadLoinc(myFiles.getFiles(), mySrd); - verify(myTermCodeSystemStorageSvc, times(2)).storeNewCodeSystemVersion(mySystemCaptor.capture(), myCsvCaptor.capture(), any(RequestDetails.class), myValueSetsCaptor.capture(), myConceptMapCaptor.capture()); + verify(myTermCodeSystemStorageSvc, times(3)).storeNewCodeSystemVersion(mySystemCaptor.capture(), myCsvCaptor.capture(), any(RequestDetails.class), myValueSetsCaptor.capture(), myConceptMapCaptor.capture()); loincCS = mySystemCaptor.getValue(); assertEquals("2.68", loincCS.getVersion()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java index 57e543a57e5..79f2c37836c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplR4Test.java @@ -12,7 +12,6 @@ import ca.uhn.fhir.jpa.entity.TermConceptMapGroup; import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement; import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget; import ca.uhn.fhir.jpa.entity.TermValueSet; -import ca.uhn.fhir.model.dstu2.valueset.ContentTypeEnum; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.CanonicalType; @@ -25,7 +24,6 @@ import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence; import org.hl7.fhir.r4.model.UriType; import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.codesystems.HttpVerb; -import org.hl7.fhir.utilities.validation.ValidationOptions; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -1787,10 +1785,10 @@ public class TerminologySvcImplR4Test extends BaseTermR4Test { createCodeSystem(); IValidationSupport.CodeValidationResult validation = myTermSvc.validateCode(new ValidationSupportContext(myValidationSupport), new ConceptValidationOptions(), CS_URL, "ParentWithNoChildrenA", null, null); - assertEquals(true, validation.isOk()); + assertTrue(validation.isOk()); validation = myTermSvc.validateCode(new ValidationSupportContext(myValidationSupport), new ConceptValidationOptions(), CS_URL, "ZZZZZZZ", null, null); - assertEquals(false, validation.isOk()); + assertFalse(validation.isOk()); } @Test @@ -1927,8 +1925,8 @@ public class TerminologySvcImplR4Test extends BaseTermR4Test { codeSystem .addConcept().setCode("C").setDisplay("Code C"); - myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified(); - codes = myTermSvc.findCodesBelow(id.getIdPartAsLong(), id.getVersionIdPartAsLong(), "C"); + IIdType id_v2 = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified(); + codes = myTermSvc.findCodesBelow(id_v2.getIdPartAsLong(), id_v2.getVersionIdPartAsLong(), "C"); assertThat(toCodes(codes), containsInAnyOrder("C")); runInTransaction(() -> { @@ -1939,7 +1937,7 @@ public class TerminologySvcImplR4Test extends BaseTermR4Test { Set termConcepts_updated = new HashSet<>(termCodeSystemVersion_2.getConcepts()); assertThat(toCodes(termConcepts_updated), containsInAnyOrder("A", "B", "C")); - TermCodeSystem termCodeSystem = myTermCodeSystemDao.findByResourcePid(id.getIdPartAsLong()); + TermCodeSystem termCodeSystem = myTermCodeSystemDao.findByResourcePid(id_v2.getIdPartAsLong()); assertEquals("2", termCodeSystem.getCurrentVersion().getCodeSystemVersionId()); }); } From 5a0bce915584881d0075cc3a71acf1549995e1b2 Mon Sep 17 00:00:00 2001 From: ianmarshall Date: Tue, 25 Aug 2020 12:45:53 -0400 Subject: [PATCH 6/8] Changes to enable request delete of specific Code System version. --- .../HapiFhirResourceDaoCodeSystemUtil.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/HapiFhirResourceDaoCodeSystemUtil.java diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/HapiFhirResourceDaoCodeSystemUtil.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/HapiFhirResourceDaoCodeSystemUtil.java new file mode 100644 index 00000000000..72cf8ac39ab --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/HapiFhirResourceDaoCodeSystemUtil.java @@ -0,0 +1,28 @@ +package ca.uhn.fhir.jpa.dao; + +import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; +import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao; +import ca.uhn.fhir.jpa.entity.TermCodeSystem; +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; +import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +public class HapiFhirResourceDaoCodeSystemUtil { + + static public void deleteCodeSystemEntities(ITermCodeSystemDao theCsDao, ITermCodeSystemVersionDao theCsvDao, + ITermDeferredStorageSvc theTermDeferredStorageSvc, String theCodeSystemUrl, + String theCodeSystemVersion) { + if (isNotBlank(theCodeSystemUrl)) { + TermCodeSystem persCs = theCsDao.findByCodeSystemUri(theCodeSystemUrl); + if (persCs != null) { + if (theCodeSystemVersion != null) { + TermCodeSystemVersion persCsVersion = theCsvDao.findByCodeSystemPidAndVersion(persCs.getPid(), theCodeSystemVersion); + theTermDeferredStorageSvc.deleteCodeSystemVersion(persCsVersion); + } else { + theTermDeferredStorageSvc.deleteCodeSystem(persCs); + } + } + } + } +} From 92313cd7703cb71c5e735fa0898b33677578fa8d Mon Sep 17 00:00:00 2001 From: ianmarshall Date: Wed, 26 Aug 2020 16:23:17 -0400 Subject: [PATCH 7/8] Tests and fixes to support versioned CodeSystem lookup and subsume operations --- .../dstu3/FhirResourceDaoCodeSystemDstu3.java | 13 +- .../dao/r4/FhirResourceDaoCodeSystemR4.java | 13 +- .../dao/r5/FhirResourceDaoCodeSystemR5.java | 13 +- .../fhir/jpa/term/BaseTermReadSvcImpl.java | 55 +- .../term/TermCodeSystemStorageSvcImpl.java | 14 +- .../uhn/fhir/jpa/term/api/ITermReadSvc.java | 2 +- .../r4/FhirResourceDaoR4CodeSystemTest.java | 130 ++- .../r4/ResourceProviderR4CodeSystemTest.java | 3 + ...urceProviderR4CodeSystemVersionedTest.java | 769 ++++++++++++++++++ .../r4/ResourceProviderR4ValueSetTest.java | 34 + .../term/TermCodeSystemStorageSvcTest.java | 76 -- .../resources/extensional-case-3-cs-v1.xml | 101 +++ .../resources/extensional-case-3-cs-v2.xml | 101 +++ 13 files changed, 1205 insertions(+), 119 deletions(-) create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemVersionedTest.java create mode 100644 hapi-fhir-jpaserver-base/src/test/resources/extensional-case-3-cs-v1.xml create mode 100644 hapi-fhir-jpaserver-base/src/test/resources/extensional-case-3-cs-v2.xml diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java index d10c33c105c..621d83d5e9a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCodeSystemDstu3.java @@ -112,16 +112,17 @@ public class FhirResourceDaoCodeSystemDstu3 extends BaseHapiFhirResourceDao theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, Coding theCodingA, Coding theCodingB, IPrimitiveType theVersion, RequestDetails theRequestDetails) { - return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theVersion); + String codingBVersion = theCodingB != null ? theCodingB.getVersion() : null; + String codingAVersion = theCodingA != null ? theCodingA.getVersion() : null; + return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theVersion, codingAVersion, codingBVersion); } @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java index 1b95f658efe..d421f52354c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCodeSystemR4.java @@ -107,16 +107,17 @@ public class FhirResourceDaoCodeSystemR4 extends BaseHapiFhirResourceDao theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, Coding theCodingA, Coding theCodingB, IPrimitiveType theVersion, RequestDetails theRequestDetails) { - return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theVersion); + String codingBVersion = theCodingB != null ? theCodingB.getVersion() : null; + String codingAVersion = theCodingA != null ? theCodingA.getVersion() : null; + return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theVersion, codingAVersion, codingBVersion); } @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java index 8a28cdeeeb0..29a2f295eb9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java @@ -109,16 +109,17 @@ public class FhirResourceDaoCodeSystemR5 extends BaseHapiFhirResourceDao theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, Coding theCodingA, Coding theCodingB, IPrimitiveType theVersion, RequestDetails theRequestDetails) { - return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theVersion); + String codingBVersion = theCodingB != null ? theCodingB.getVersion() : null; + String codingAVersion = theCodingA != null ? theCodingA.getVersion() : null; + return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theVersion, codingAVersion, codingBVersion); } @Override diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java index 69e1113fb09..7f0a90be65a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java @@ -1380,7 +1380,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { return txTemplate.execute(t -> { TermCodeSystemVersion csv = getCurrentCodeSystemVersionForVersion(theCodeSystem, theVersion); if (csv == null) { - return null; + return Optional.empty(); } return myConceptDao.findByCodeSystemAndCode(csv, theCode); }); @@ -1392,9 +1392,9 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { if (theVersion != null) { key.append("_").append(theVersion); } - TermCodeSystemVersion retVal = myCodeSystemCurrentVersionCache.get(key.toString(), uri -> myTxTemplate.execute(tx -> { + TermCodeSystemVersion retVal = myCodeSystemCurrentVersionCache.get(key.toString(), t -> myTxTemplate.execute(tx -> { TermCodeSystemVersion csv = null; - TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(uri); + TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(theUri); if (cs != null) { if (theVersion != null) { csv = myCodeSystemVersionDao.findByCodeSystemPidAndVersion(cs.getPid(), theVersion); @@ -1819,28 +1819,32 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } @Override - public IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB) { - return subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, null); + public IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, + IPrimitiveType theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB) { + return subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, null, null, null); } @Override @Transactional - public IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB, IPrimitiveType theSystemVersion) { - VersionIndependentConcept conceptA = toConcept(theCodeA, theSystem, theCodingA); - VersionIndependentConcept conceptB = toConcept(theCodeB, theSystem, theCodingB); - String systemVersion = null; - if (theSystemVersion != null) { - systemVersion = theSystemVersion.getValue(); - } + public IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, + IPrimitiveType theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB, + IPrimitiveType theSystemVersion, String theCodingAVersion, + String theCodingBVersion) { + VersionIndependentConceptWithSystemVersion conceptA = toConcept(theCodeA, theSystem, theCodingA, theSystemVersion, theCodingAVersion); + VersionIndependentConceptWithSystemVersion conceptB = toConcept(theCodeB, theSystem, theCodingB, theSystemVersion, theCodingBVersion); if (!StringUtils.equals(conceptA.getSystem(), conceptB.getSystem())) { throw new InvalidRequestException("Unable to test subsumption across different code systems"); } - TermConcept codeA = findCode(conceptA.getSystem(), conceptA.getCode(), systemVersion) + if (!StringUtils.equals(conceptA.getCodeSystemVersion(), conceptB.getCodeSystemVersion())) { + throw new InvalidRequestException("Unable to test subsumption across different code system versions"); + } + + TermConcept codeA = findCode(conceptA.getSystem(), conceptA.getCode(), conceptA.getCodeSystemVersion()) .orElseThrow(() -> new InvalidRequestException("Unknown code: " + conceptA)); - TermConcept codeB = findCode(conceptB.getSystem(), conceptB.getCode(), systemVersion) + TermConcept codeB = findCode(conceptB.getSystem(), conceptB.getCode(), conceptB.getCodeSystemVersion()) .orElseThrow(() -> new InvalidRequestException("Unknown code: " + conceptB)); FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager); @@ -2411,14 +2415,33 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } @NotNull - private static VersionIndependentConcept toConcept(IPrimitiveType theCodeType, IPrimitiveType theSystemType, IBaseCoding theCodingType) { + private VersionIndependentConceptWithSystemVersion toConcept(IPrimitiveType theCodeType, IPrimitiveType theSystemType, + IBaseCoding theCodingType, IPrimitiveType theSystemVersionType, + String theCodingVersionType) { String code = theCodeType != null ? theCodeType.getValueAsString() : null; String system = theSystemType != null ? theSystemType.getValueAsString() : null; + String systemVersion = theSystemVersionType != null ? theSystemVersionType.getValueAsString() : null; if (theCodingType != null) { code = theCodingType.getCode(); system = theCodingType.getSystem(); + systemVersion = theCodingVersionType; } - return new VersionIndependentConcept(system, code); + return new VersionIndependentConceptWithSystemVersion(system, code, systemVersion); + } + + private static class VersionIndependentConceptWithSystemVersion extends VersionIndependentConcept { + + String myCodeSystemVersion; + + public VersionIndependentConceptWithSystemVersion(String theSystem, String theCode, String theSystemVersion) { + super(theSystem, theCode); + myCodeSystemVersion = theSystemVersion; + } + + public String getCodeSystemVersion() { + return myCodeSystemVersion; + } + } /** diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java index d9d7189534b..6230a94cc35 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java @@ -234,9 +234,19 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { @Override @Transactional(propagation = Propagation.NEVER) public void deleteCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) { - ourLog.info(" * Deleting code system version {}", theCodeSystemVersion.getPid()); - + TermCodeSystem termCodeSystem = theCodeSystemVersion.getCodeSystem(); + ourLog.info(" * Deleting code system version {}", theCodeSystemVersion.getCodeSystemVersionId()); deleteCodeSystemVersion(theCodeSystemVersion.getPid()); + + TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); + txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); + txTemplate.executeWithoutResult(t -> { + if (myCodeSystemVersionDao.findByCodeSystemPid(termCodeSystem.getPid()).size() == 0) { + ourLog.info(" * Deleting code system {}", termCodeSystem.getPid()); + deleteCodeSystem(termCodeSystem); + } + }); + } /** diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java index 9e6e340e5bd..124cf2d6450 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/api/ITermReadSvc.java @@ -105,7 +105,7 @@ public interface ITermReadSvc extends IValidationSupport { IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB); - IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB, IPrimitiveType theSystemVersion); + IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB, IPrimitiveType theSystemVersion, String theCodingAVersion, String theCodingBVersion); void preExpandDeferredValueSetsToTerminologyTables(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CodeSystemTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CodeSystemTest.java index df2905456c4..4328183f249 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CodeSystemTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CodeSystemTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.dao.r4; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl; import org.apache.commons.io.IOUtils; import org.hl7.fhir.instance.model.api.IIdType; @@ -8,11 +9,14 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; public class FhirResourceDaoR4CodeSystemTest extends BaseJpaR4Test { @@ -35,15 +39,7 @@ public class FhirResourceDaoR4CodeSystemTest extends BaseJpaR4Test { @Test public void testDeleteLargeCompleteCodeSystem() { - CodeSystem cs = new CodeSystem(); - cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE); - cs.setUrl("http://foo"); - for (int i = 0; i < 222; i++) { - cs.addConcept().setCode("CODE" + i); - } - IIdType id = myCodeSystemDao.create(cs).getId().toUnqualifiedVersionless(); - myTerminologyDeferredStorageSvc.saveDeferred(); - myTerminologyDeferredStorageSvc.saveDeferred(); + IIdType id = createLargeCodeSystem(null); runInTransaction(() -> { assertEquals(1, myTermCodeSystemDao.count()); @@ -72,6 +68,122 @@ public class FhirResourceDaoR4CodeSystemTest extends BaseJpaR4Test { } + @Test + public void testDeleteCodeSystemVersion() { + + // Create code system with two versions. + IIdType id_first = createLargeCodeSystem("1"); + + runInTransaction(() -> { + assertEquals(1, myTermCodeSystemDao.count()); + assertNotNull(myTermCodeSystemDao.findByCodeSystemUri("http://foo")); + assertEquals(1, myTermCodeSystemVersionDao.count()); + List resourceList = myResourceTableDao.findAll(); + assertEquals(222, myTermConceptDao.count()); + assertEquals(1, resourceList.size()); + assertNull(resourceList.get(0).getDeleted()); + }); + + IIdType id_second = createLargeCodeSystem("2"); + + runInTransaction(() -> { + assertEquals(1, myTermCodeSystemDao.count()); + assertNotNull(myTermCodeSystemDao.findByCodeSystemUri("http://foo")); + assertEquals(2, myTermCodeSystemVersionDao.count()); + assertEquals(444, myTermConceptDao.count()); + List resourceList = myResourceTableDao.findAll(); + assertEquals(2, resourceList.size()); + long active = resourceList + .stream() + .filter(t -> t.getDeleted() == null).count(); + assertEquals(2, active); + }); + + // Attempt to delete first version + myCodeSystemDao.delete(id_first, mySrd); + + // Only the resource will be deleted initially + runInTransaction(() -> { + assertEquals(1, myTermCodeSystemDao.count()); + assertNotNull(myTermCodeSystemDao.findByCodeSystemUri("http://foo")); + assertEquals(2, myTermCodeSystemVersionDao.count()); + assertEquals(444, myTermConceptDao.count()); + List resourceList = myResourceTableDao.findAll(); + assertEquals(2, resourceList.size()); + long active = resourceList + .stream() + .filter(t -> t.getDeleted() == null).count(); + assertEquals(1, active); + }); + + // Now the background scheduler will do its thing + myTerminologyDeferredStorageSvc.saveDeferred(); + + // Entities for first resource should be gone now. + runInTransaction(() -> { + assertEquals(1, myTermCodeSystemDao.count()); + assertNotNull(myTermCodeSystemDao.findByCodeSystemUri("http://foo")); + assertEquals(1, myTermCodeSystemVersionDao.count()); + assertEquals(222, myTermConceptDao.count()); + List resourceList = myResourceTableDao.findAll(); + assertEquals(2, resourceList.size()); + long active = resourceList + .stream() + .filter(t -> t.getDeleted() == null).count(); + assertEquals(1, active); + }); + + // Attempt to delete second version + myCodeSystemDao.delete(id_second, mySrd); + + // Only the resource will be deleted initially + runInTransaction(() -> { + assertEquals(1, myTermCodeSystemDao.count()); + assertNotNull(myTermCodeSystemDao.findByCodeSystemUri("http://foo")); + assertEquals(1, myTermCodeSystemVersionDao.count()); + assertEquals(222, myTermConceptDao.count()); + List resourceList = myResourceTableDao.findAll(); + assertEquals(2, resourceList.size()); + long active = resourceList + .stream() + .filter(t -> t.getDeleted() == null).count(); + assertEquals(0, active); + }); + + // Now the background scheduler will do its thing + myTerminologyDeferredStorageSvc.saveDeferred(); + + // The remaining versions and Code System entities should be gone now. + runInTransaction(() -> { + assertEquals(0, myTermCodeSystemDao.count()); + assertNull(myTermCodeSystemDao.findByCodeSystemUri("http://foo")); + assertEquals(0, myTermCodeSystemVersionDao.count()); + List resourceList = myResourceTableDao.findAll(); + assertEquals(2, resourceList.size()); + long active = resourceList + .stream() + .filter(t -> t.getDeleted() == null).count(); + assertEquals(0, active); + }); + + } + + private IIdType createLargeCodeSystem(String theVersion) { + CodeSystem cs = new CodeSystem(); + cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE); + cs.setUrl("http://foo"); + if (theVersion != null) { + cs.setVersion(theVersion); + } + for (int i = 0; i < 222; i++) { + cs.addConcept().setCode("CODE" + i); + } + IIdType id = myCodeSystemDao.create(cs).getId().toUnqualifiedVersionless(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + return id; + } + @AfterAll public static void afterClassClearContext() { TermReindexingSvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemTest.java index 8fc41cbb4ab..0ed780b76e4 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemTest.java @@ -325,6 +325,7 @@ public class ResourceProviderR4CodeSystemTest extends BaseResourceProviderR4Test .withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("FOO")) .andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentB")) .execute(); + fail(); } catch (InvalidRequestException e) { assertEquals("HTTP 400 Bad Request: Unknown code: [http://parentchild|FOO]", e.getMessage()); } @@ -340,6 +341,7 @@ public class ResourceProviderR4CodeSystemTest extends BaseResourceProviderR4Test .withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentB")) .andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("FOO")) .execute(); + fail(); } catch (InvalidRequestException e) { assertEquals("HTTP 400 Bad Request: Unknown code: [http://parentchild|FOO]", e.getMessage()); } @@ -355,6 +357,7 @@ public class ResourceProviderR4CodeSystemTest extends BaseResourceProviderR4Test .withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD + "A").setCode("ChildAA")) .andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD + "B").setCode("ParentA")) .execute(); + fail(); } catch (InvalidRequestException e) { assertEquals("HTTP 400 Bad Request: Unable to test subsumption across different code systems", e.getMessage()); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemVersionedTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemVersionedTest.java new file mode 100644 index 00000000000..d6cce634524 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4CodeSystemVersionedTest.java @@ -0,0 +1,769 @@ +package ca.uhn.fhir.jpa.provider.r4; + +import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest; +import ca.uhn.fhir.jpa.model.util.JpaConstants; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Enumerations; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.UriType; +import org.hl7.fhir.r4.model.ValueSet; +import org.hl7.fhir.r4.model.codesystems.ConceptSubsumptionOutcome; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.transaction.annotation.Transactional; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class ResourceProviderR4CodeSystemVersionedTest extends BaseResourceProviderR4Test { + + private static final String SYSTEM_PARENTCHILD = "http://parentchild"; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderR4CodeSystemVersionedTest.class); + + @BeforeEach + @Transactional + public void before02() throws IOException { + CodeSystem cs = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs-v1.xml"); + myCodeSystemDao.create(cs, mySrd); + + cs = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs-v2.xml"); + myCodeSystemDao.create(cs, mySrd); + + ValueSet upload = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); + myValueSetDao.create(upload, mySrd).getId().toUnqualifiedVersionless(); + + CodeSystem parentChildCs = new CodeSystem(); + parentChildCs.setUrl(SYSTEM_PARENTCHILD); + parentChildCs.setVersion("1"); + parentChildCs.setStatus(Enumerations.PublicationStatus.ACTIVE); + parentChildCs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE); + parentChildCs.setHierarchyMeaning(CodeSystem.CodeSystemHierarchyMeaning.ISA); + + CodeSystem.ConceptDefinitionComponent parentA = parentChildCs.addConcept().setCode("ParentA").setDisplay("Parent A"); + parentA.addConcept().setCode("ChildAA").setDisplay("Child AA"); + parentA.addConcept().setCode("ParentC").setDisplay("Parent C"); + parentChildCs.addConcept().setCode("ParentB").setDisplay("Parent B"); + + myCodeSystemDao.create(parentChildCs); + + parentChildCs = new CodeSystem(); + parentChildCs.setVersion("2"); + parentChildCs.setUrl(SYSTEM_PARENTCHILD); + parentChildCs.setStatus(Enumerations.PublicationStatus.ACTIVE); + parentChildCs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE); + parentChildCs.setHierarchyMeaning(CodeSystem.CodeSystemHierarchyMeaning.ISA); + + parentA = parentChildCs.addConcept().setCode("ParentA").setDisplay("Parent A v2"); + parentA.addConcept().setCode("ChildAA").setDisplay("Child AA v2"); + parentA.addConcept().setCode("ParentB").setDisplay("Parent B v2"); + parentChildCs.addConcept().setCode("ParentC").setDisplay("Parent C v2"); + + myCodeSystemDao.create(parentChildCs); + + } + + @Test + public void testLookupOnExternalCodeMultiVersion() { + ResourceProviderR4ValueSetTest.createExternalCs(myCodeSystemDao, myResourceTableDao, myTermCodeSystemStorageSvc, mySrd, "1"); + ResourceProviderR4ValueSetTest.createExternalCs(myCodeSystemDao, myResourceTableDao, myTermCodeSystemStorageSvc, mySrd, "2"); + + // First test with no version specified (should return from last version created) + Parameters respParam = myClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "code", new CodeType("ParentA")) + .andParameter("system", new UriType(FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM)) + .execute(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals("name", respParam.getParameter().get(0).getName()); + assertEquals("SYSTEM NAME", ((StringType) respParam.getParameter().get(0).getValue()).getValue()); + assertEquals("version", respParam.getParameter().get(1).getName()); + assertEquals("2", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("display", respParam.getParameter().get(2).getName()); + assertEquals("Parent A2", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); + assertEquals("abstract", respParam.getParameter().get(3).getName()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue()); + + // With HTTP GET + respParam = myClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "code", new CodeType("ParentA")) + .andParameter("system", new UriType(FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM)) + .useHttpGet() + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals("name", respParam.getParameter().get(0).getName()); + assertEquals(("SYSTEM NAME"), ((StringType) respParam.getParameter().get(0).getValue()).getValue()); + assertEquals("version", respParam.getParameter().get(1).getName()); + assertEquals(("2"), ((StringType) respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("display", respParam.getParameter().get(2).getName()); + assertEquals("Parent A2", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); + assertEquals("abstract", respParam.getParameter().get(3).getName()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue()); + + // Test with version 1 specified. + respParam = myClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "code", new CodeType("ParentA")) + .andParameter("system", new UriType(FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM)) + .andParameter("version", new StringType("1")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals("name", respParam.getParameter().get(0).getName()); + assertEquals("SYSTEM NAME", ((StringType) respParam.getParameter().get(0).getValue()).getValue()); + assertEquals("version", respParam.getParameter().get(1).getName()); + assertEquals("1", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("display", respParam.getParameter().get(2).getName()); + assertEquals("Parent A1", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); + assertEquals("abstract", respParam.getParameter().get(3).getName()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue()); + + // With HTTP GET + respParam = myClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "code", new CodeType("ParentA")) + .andParameter("system", new UriType(FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM)) + .andParameter("version", new StringType("1")) + .useHttpGet() + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals("name", respParam.getParameter().get(0).getName()); + assertEquals(("SYSTEM NAME"), ((StringType) respParam.getParameter().get(0).getValue()).getValue()); + assertEquals("version", respParam.getParameter().get(1).getName()); + assertEquals(("1"), ((StringType) respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("display", respParam.getParameter().get(2).getName()); + assertEquals("Parent A1", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); + assertEquals("abstract", respParam.getParameter().get(3).getName()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue()); + + // Test with version 2 specified. + respParam = myClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "code", new CodeType("ParentA")) + .andParameter("system", new UriType(FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM)) + .andParameter("version", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals("name", respParam.getParameter().get(0).getName()); + assertEquals("SYSTEM NAME", ((StringType) respParam.getParameter().get(0).getValue()).getValue()); + assertEquals("version", respParam.getParameter().get(1).getName()); + assertEquals("2", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("display", respParam.getParameter().get(2).getName()); + assertEquals("Parent A2", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); + assertEquals("abstract", respParam.getParameter().get(3).getName()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue()); + + // With HTTP GET + respParam = myClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "code", new CodeType("ParentA")) + .andParameter("system", new UriType(FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM)) + .andParameter("version", new StringType("2")) + .useHttpGet() + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals("name", respParam.getParameter().get(0).getName()); + assertEquals(("SYSTEM NAME"), ((StringType) respParam.getParameter().get(0).getValue()).getValue()); + assertEquals("version", respParam.getParameter().get(1).getName()); + assertEquals(("2"), ((StringType) respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("display", respParam.getParameter().get(2).getName()); + assertEquals("Parent A2", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); + assertEquals("abstract", respParam.getParameter().get(3).getName()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue()); + } + + @Test + public void testLookupOperationByCodeAndSystemBuiltInCode() { + // First test with no version specified (should return the one and only version defined). + Parameters respParam = myClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "code", new CodeType("ACSN")) + .andParameter("system", new UriType("http://terminology.hl7.org/CodeSystem/v2-0203")) + .execute(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals("name", respParam.getParameter().get(0).getName()); + assertEquals("v2.0203", ((StringType) respParam.getParameter().get(0).getValue()).getValue()); + assertEquals("version", respParam.getParameter().get(1).getName()); + assertEquals("2.9", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("display", respParam.getParameter().get(2).getName()); + assertEquals("Accession ID", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); + assertEquals("abstract", respParam.getParameter().get(3).getName()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue()); + + // Repeat with version specified. + respParam = myClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "code", new CodeType("ACSN")) + .andParameter("system", new UriType("http://terminology.hl7.org/CodeSystem/v2-0203")) + .andParameter("version", new StringType("2.9")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals("name", respParam.getParameter().get(0).getName()); + assertEquals("v2.0203", ((StringType) respParam.getParameter().get(0).getValue()).getValue()); + assertEquals("version", respParam.getParameter().get(1).getName()); + assertEquals("2.9", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("display", respParam.getParameter().get(2).getName()); + assertEquals("Accession ID", ((StringType) respParam.getParameter().get(2).getValue()).getValue()); + assertEquals("abstract", respParam.getParameter().get(3).getName()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue()); + } + + @Test + public void testLookupOperationByCodeAndSystemBuiltInNonexistentVersion() { + try { + myClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "code", new CodeType("ACSN")) + .andParameter("system", new UriType("http://hl7.org/fhir/v2/0203")) + .andParameter("version", new StringType("2.8")) + .execute(); + fail(); + } catch (ResourceNotFoundException e) { + ourLog.info("Lookup failed as expected"); + } + } + + @Test + public void testLookupOperationByCodeAndSystemUserDefinedCode() { + // First test with no version specified (should return from last version created) + Parameters respParam = myClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "code", new CodeType("8450-9")) + .andParameter("system", new UriType("http://acme.org")) + .execute(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals("name", respParam.getParameter().get(0).getName()); + assertEquals(("ACME Codes"), ((StringType) respParam.getParameter().get(0).getValue()).getValue()); + assertEquals("version", respParam.getParameter().get(1).getName()); + assertEquals("2", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("display", respParam.getParameter().get(2).getName()); + assertEquals(("Systolic blood pressure--expiration v2"), ((StringType) respParam.getParameter().get(2).getValue()).getValue()); + assertEquals("abstract", respParam.getParameter().get(3).getName()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue()); + + // Test with version 1 specified. + respParam = myClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "code", new CodeType("8450-9")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("version", new StringType("1")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals("name", respParam.getParameter().get(0).getName()); + assertEquals(("ACME Codes"), ((StringType) respParam.getParameter().get(0).getValue()).getValue()); + assertEquals("version", respParam.getParameter().get(1).getName()); + assertEquals("1", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("display", respParam.getParameter().get(2).getName()); + assertEquals(("Systolic blood pressure--expiration"), ((StringType) respParam.getParameter().get(2).getValue()).getValue()); + assertEquals("abstract", respParam.getParameter().get(3).getName()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue()); + + // Test with version 2 specified + respParam = myClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "code", new CodeType("8450-9")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("version", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals("name", respParam.getParameter().get(0).getName()); + assertEquals(("ACME Codes"), ((StringType) respParam.getParameter().get(0).getValue()).getValue()); + assertEquals("version", respParam.getParameter().get(1).getName()); + assertEquals("2", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("display", respParam.getParameter().get(2).getName()); + assertEquals(("Systolic blood pressure--expiration v2"), ((StringType) respParam.getParameter().get(2).getValue()).getValue()); + assertEquals("abstract", respParam.getParameter().get(3).getName()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue()); + } + + @Test + public void testLookupOperationByCodeAndSystemUserDefinedNonExistentVersion() { + try { + myClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "code", new CodeType("8450-9")) + .andParameter("system", new UriType("http://acme.org")) + .andParameter("version", new StringType("3")) + .execute(); + fail(); + } catch (ResourceNotFoundException e) { + ourLog.info("Lookup failed as expected"); + } + } + + @Test + public void testLookupOperationByCoding() { + // First test with no version specified (should return from last version created) + Parameters respParam = myClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "coding", new Coding().setSystem("http://acme.org").setCode("8450-9")) + .execute(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals("name", respParam.getParameter().get(0).getName()); + assertEquals(("ACME Codes"), ((StringType) respParam.getParameter().get(0).getValue()).getValue()); + assertEquals("version", respParam.getParameter().get(1).getName()); + assertEquals("2", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("display", respParam.getParameter().get(2).getName()); + assertEquals(("Systolic blood pressure--expiration v2"), ((StringType) respParam.getParameter().get(2).getValue()).getValue()); + assertEquals("abstract", respParam.getParameter().get(3).getName()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue()); + + // Test with version set to 1 + respParam = myClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "coding", new Coding().setSystem("http://acme.org").setCode("8450-9").setVersion("1")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals("name", respParam.getParameter().get(0).getName()); + assertEquals(("ACME Codes"), ((StringType) respParam.getParameter().get(0).getValue()).getValue()); + assertEquals("version", respParam.getParameter().get(1).getName()); + assertEquals("1", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("display", respParam.getParameter().get(2).getName()); + assertEquals(("Systolic blood pressure--expiration"), ((StringType) respParam.getParameter().get(2).getValue()).getValue()); + assertEquals("abstract", respParam.getParameter().get(3).getName()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue()); + + // Test with version set to 2 + respParam = myClient + .operation() + .onType(CodeSystem.class) + .named("lookup") + .withParameter(Parameters.class, "coding", new Coding().setSystem("http://acme.org").setCode("8450-9").setVersion("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals("name", respParam.getParameter().get(0).getName()); + assertEquals(("ACME Codes"), ((StringType) respParam.getParameter().get(0).getValue()).getValue()); + assertEquals("version", respParam.getParameter().get(1).getName()); + assertEquals("2", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); + assertEquals("display", respParam.getParameter().get(2).getName()); + assertEquals(("Systolic blood pressure--expiration v2"), ((StringType) respParam.getParameter().get(2).getValue()).getValue()); + assertEquals("abstract", respParam.getParameter().get(3).getName()); + assertEquals(false, ((BooleanType) respParam.getParameter().get(3).getValue()).getValue()); + + } + + @Test + public void testSubsumesOnCodes_Subsumes() { + // First test with no version specified (should return result for last version created). + Parameters respParam = myClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_SUBSUMES) + .withParameter(Parameters.class, "codeA", new CodeType("ParentB")) + .andParameter("codeB", new CodeType("ParentA")) + .andParameter("system", new UriType(SYSTEM_PARENTCHILD)) + .execute(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals(1, respParam.getParameter().size()); + assertEquals("outcome", respParam.getParameter().get(0).getName()); + assertEquals(ConceptSubsumptionOutcome.SUBSUMES.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue()); + + // Test with version set to 1. + respParam = myClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_SUBSUMES) + .withParameter(Parameters.class, "codeA", new CodeType("ParentC")) + .andParameter("codeB", new CodeType("ParentA")) + .andParameter("system", new UriType(SYSTEM_PARENTCHILD)) + .andParameter("version", new StringType("1")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals(1, respParam.getParameter().size()); + assertEquals("outcome", respParam.getParameter().get(0).getName()); + assertEquals(ConceptSubsumptionOutcome.SUBSUMES.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue()); + + // Test with version set to 2. + respParam = myClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_SUBSUMES) + .withParameter(Parameters.class, "codeA", new CodeType("ParentB")) + .andParameter("codeB", new CodeType("ParentA")) + .andParameter("system", new UriType(SYSTEM_PARENTCHILD)) + .andParameter("version", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals(1, respParam.getParameter().size()); + assertEquals("outcome", respParam.getParameter().get(0).getName()); + assertEquals(ConceptSubsumptionOutcome.SUBSUMES.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue()); + + } + + @Test + public void testSubsumesOnCodes_Subsumedby() { + // First test with no version specified (should return result for last version created). + Parameters respParam = myClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_SUBSUMES) + .withParameter(Parameters.class, "codeA", new CodeType("ParentA")) + .andParameter("codeB", new CodeType("ParentB")) + .andParameter("system", new UriType(SYSTEM_PARENTCHILD)) + .execute(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals(1, respParam.getParameter().size()); + assertEquals("outcome", respParam.getParameter().get(0).getName()); + assertEquals(ConceptSubsumptionOutcome.SUBSUMEDBY.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue()); + + // Test with version set to 1. + respParam = myClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_SUBSUMES) + .withParameter(Parameters.class, "codeA", new CodeType("ParentA")) + .andParameter("codeB", new CodeType("ParentC")) + .andParameter("system", new UriType(SYSTEM_PARENTCHILD)) + .andParameter("version", new StringType("1")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals(1, respParam.getParameter().size()); + assertEquals("outcome", respParam.getParameter().get(0).getName()); + assertEquals(ConceptSubsumptionOutcome.SUBSUMEDBY.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue()); + + // Test with version set to 2. + respParam = myClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_SUBSUMES) + .withParameter(Parameters.class, "codeA", new CodeType("ParentA")) + .andParameter("codeB", new CodeType("ParentB")) + .andParameter("system", new UriType(SYSTEM_PARENTCHILD)) + .andParameter("version", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals(1, respParam.getParameter().size()); + assertEquals("outcome", respParam.getParameter().get(0).getName()); + assertEquals(ConceptSubsumptionOutcome.SUBSUMEDBY.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue()); + } + + @Test + public void testSubsumesOnCodes_Disjoint() { + // First test with no version specified (should return result for last version created). + Parameters respParam = myClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_SUBSUMES) + .withParameter(Parameters.class, "codeA", new CodeType("ParentA")) + .andParameter("codeB", new CodeType("ParentC")) + .andParameter("system", new UriType(SYSTEM_PARENTCHILD)) + .execute(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals(1, respParam.getParameter().size()); + assertEquals("outcome", respParam.getParameter().get(0).getName()); + assertEquals(ConceptSubsumptionOutcome.NOTSUBSUMED.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue()); + + // Test with version set to 1 + respParam = myClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_SUBSUMES) + .withParameter(Parameters.class, "codeA", new CodeType("ParentA")) + .andParameter("codeB", new CodeType("ParentB")) + .andParameter("system", new UriType(SYSTEM_PARENTCHILD)) + .andParameter("version", new StringType("1")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals(1, respParam.getParameter().size()); + assertEquals("outcome", respParam.getParameter().get(0).getName()); + assertEquals(ConceptSubsumptionOutcome.NOTSUBSUMED.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue()); + + // Test with version set to 2 + respParam = myClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_SUBSUMES) + .withParameter(Parameters.class, "codeA", new CodeType("ParentA")) + .andParameter("codeB", new CodeType("ParentC")) + .andParameter("system", new UriType(SYSTEM_PARENTCHILD)) + .andParameter("version", new StringType("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals(1, respParam.getParameter().size()); + assertEquals("outcome", respParam.getParameter().get(0).getName()); + assertEquals(ConceptSubsumptionOutcome.NOTSUBSUMED.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue()); + + } + + @Test + public void testSubsumesOnCodings_MismatchedCsVersions() { + try { + myClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_SUBSUMES) + .withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ChildAA").setVersion("1")) + .andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentA").setVersion("2")) + .execute(); + fail(); + } catch (InvalidRequestException e) { + assertEquals("HTTP 400 Bad Request: Unable to test subsumption across different code system versions", e.getMessage()); + } + } + + + @Test + public void testSubsumesOnCodings_Subsumes() { + // First test with no version specified (should return result for last version created). + Parameters respParam = myClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_SUBSUMES) + .withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentB")) + .andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentA")) + .execute(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals(1, respParam.getParameter().size()); + assertEquals("outcome", respParam.getParameter().get(0).getName()); + assertEquals(ConceptSubsumptionOutcome.SUBSUMES.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue()); + + // Test with version set to 1. + respParam = myClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_SUBSUMES) + .withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentC").setVersion("1")) + .andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentA").setVersion("1")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals(1, respParam.getParameter().size()); + assertEquals("outcome", respParam.getParameter().get(0).getName()); + assertEquals(ConceptSubsumptionOutcome.SUBSUMES.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue()); + + // Test with version set to 2. + respParam = myClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_SUBSUMES) + .withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentB").setVersion("2")) + .andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentA").setVersion("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals(1, respParam.getParameter().size()); + assertEquals("outcome", respParam.getParameter().get(0).getName()); + assertEquals(ConceptSubsumptionOutcome.SUBSUMES.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue()); + + } + + + @Test + public void testSubsumesOnCodings_Subsumedby() { + // First test with no version specified (should return result for last version created). + Parameters respParam = myClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_SUBSUMES) + .withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentA")) + .andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentB")) + .execute(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals(1, respParam.getParameter().size()); + assertEquals("outcome", respParam.getParameter().get(0).getName()); + assertEquals(ConceptSubsumptionOutcome.SUBSUMEDBY.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue()); + + // Test with version set to 1. + respParam = myClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_SUBSUMES) + .withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentA").setVersion("1")) + .andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentC").setVersion("1")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals(1, respParam.getParameter().size()); + assertEquals("outcome", respParam.getParameter().get(0).getName()); + assertEquals(ConceptSubsumptionOutcome.SUBSUMEDBY.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue()); + + // Test with version set to 2. + respParam = myClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_SUBSUMES) + .withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentA").setVersion("2")) + .andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentB").setVersion("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals(1, respParam.getParameter().size()); + assertEquals("outcome", respParam.getParameter().get(0).getName()); + assertEquals(ConceptSubsumptionOutcome.SUBSUMEDBY.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue()); + + } + + @Test + public void testSubsumesOnCodings_Disjoint() { + // First test with no version specified (should return result for last version created). + Parameters respParam = myClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_SUBSUMES) + .withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentA")) + .andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentC")) + .execute(); + + String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals(1, respParam.getParameter().size()); + assertEquals("outcome", respParam.getParameter().get(0).getName()); + assertEquals(ConceptSubsumptionOutcome.NOTSUBSUMED.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue()); + + // Test with version set to 1 + respParam = myClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_SUBSUMES) + .withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentA").setVersion("1")) + .andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentB").setVersion("1")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals(1, respParam.getParameter().size()); + assertEquals("outcome", respParam.getParameter().get(0).getName()); + assertEquals(ConceptSubsumptionOutcome.NOTSUBSUMED.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue()); + + // Test with version set to 2 + respParam = myClient + .operation() + .onType(CodeSystem.class) + .named(JpaConstants.OPERATION_SUBSUMES) + .withParameter(Parameters.class, "codingA", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentA").setVersion("2")) + .andParameter("codingB", new Coding().setSystem(SYSTEM_PARENTCHILD).setCode("ParentC").setVersion("2")) + .execute(); + + resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); + ourLog.info(resp); + + assertEquals(1, respParam.getParameter().size()); + assertEquals("outcome", respParam.getParameter().get(0).getName()); + assertEquals(ConceptSubsumptionOutcome.NOTSUBSUMED.toCode(), ((CodeType) respParam.getParameter().get(0).getValue()).getValue()); + + } + + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java index 6196d2d226f..0d2c8d18407 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java @@ -1135,4 +1135,38 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { return codeSystem; } + public static CodeSystem createExternalCs(IFhirResourceDao theCodeSystemDao, IResourceTableDao theResourceTableDao, ITermCodeSystemStorageSvc theTermCodeSystemStorageSvc, ServletRequestDetails theRequestDetails, String theCodeSystemVersion) { + CodeSystem codeSystem = new CodeSystem(); + codeSystem.setUrl(URL_MY_CODE_SYSTEM); + codeSystem.setContent(CodeSystemContentMode.NOTPRESENT); + codeSystem.setVersion(theCodeSystemVersion); + IIdType id = theCodeSystemDao.create(codeSystem, theRequestDetails).getId().toUnqualified(); + + ResourceTable table = theResourceTableDao.findById(id.getIdPartAsLong()).orElseThrow(IllegalStateException::new); + + TermCodeSystemVersion cs = new TermCodeSystemVersion(); + cs.setResource(table); + + TermConcept parentA = new TermConcept(cs, "ParentA").setDisplay("Parent A" + theCodeSystemVersion); + cs.getConcepts().add(parentA); + + TermConcept childAA = new TermConcept(cs, "childAA").setDisplay("Child AA" + theCodeSystemVersion); + parentA.addChild(childAA, RelationshipTypeEnum.ISA); + + TermConcept childAAA = new TermConcept(cs, "childAAA").setDisplay("Child AAA" + theCodeSystemVersion); + childAA.addChild(childAAA, RelationshipTypeEnum.ISA); + + TermConcept childAAB = new TermConcept(cs, "childAAB").setDisplay("Child AAB" + theCodeSystemVersion); + childAA.addChild(childAAB, RelationshipTypeEnum.ISA); + + TermConcept childAB = new TermConcept(cs, "childAB").setDisplay("Child AB" + theCodeSystemVersion); + parentA.addChild(childAB, RelationshipTypeEnum.ISA); + + TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B" + theCodeSystemVersion); + cs.getConcepts().add(parentB); + + theTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", theCodeSystemVersion, cs, table); + return codeSystem; + } + } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcTest.java index 3845452a093..42ace0e1efc 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcTest.java @@ -12,8 +12,6 @@ import org.junit.jupiter.api.Test; import java.util.ArrayList; import static org.junit.jupiter.api.Assertions.fail; - - import static org.junit.jupiter.api.Assertions.assertEquals; public class TermCodeSystemStorageSvcTest extends BaseJpaR4Test { @@ -80,67 +78,6 @@ public class TermCodeSystemStorageSvcTest extends BaseJpaR4Test { } - @Test - public void testDeleteCodeSystem() { - CodeSystem codeSystemToDelete = createSmallCodeSystem(); - - // Create CodeSystem resource and entity - ResourceTable codeSystemResourceEntity = (ResourceTable)myCodeSystemDao.create(codeSystemToDelete, mySrd).getEntity(); - validateCodeSystemUpdates(10); - - runInTransaction(() -> { - assertEquals(1, myTermCodeSystemDao.count()); - assertEquals(1, myTermCodeSystemVersionDao.count()); - }); - - // Attempt to delete - myCodeSystemDao.delete(codeSystemResourceEntity.getIdDt(), mySrd); - validateCodeSystemUpdates(0); - - runInTransaction(() -> { - assertEquals(0, myTermCodeSystemDao.count()); - assertEquals(0, myTermCodeSystemVersionDao.count()); - }); - - } - - @Test - public void testDeleteCodeSystemVersion() { - CodeSystem firstCodeSystemVersion = createSmallCodeSystem(); - firstCodeSystemVersion.setVersion("1"); - - // Create first CodeSystem resource and entity - ResourceTable codeSystemResourceEntity = (ResourceTable)myCodeSystemDao.create(firstCodeSystemVersion, mySrd).getEntity(); - validateCodeSystemUpdates(10); - - runInTransaction(() -> { - assertEquals(1, myTermCodeSystemDao.count()); - assertEquals(1, myTermCodeSystemVersionDao.count()); - }); - - CodeSystem secondCodeSystemVersion = createSmallCodeSystem(); - secondCodeSystemVersion.setVersion("2"); - - // Create CodeSystem resource and entity - myCodeSystemDao.create(secondCodeSystemVersion, mySrd); - validateCodeSystemUpdates(20); - - runInTransaction(() -> { - assertEquals(1, myTermCodeSystemDao.count()); - assertEquals(2, myTermCodeSystemVersionDao.count()); - }); - - // Attempt to delete first version - myCodeSystemDao.delete(codeSystemResourceEntity.getIdDt(), mySrd); - validateCodeSystemUpdates(10); - - runInTransaction(() -> { - assertEquals(1, myTermCodeSystemDao.count()); - assertEquals(1, myTermCodeSystemVersionDao.count()); - }); - - } - private CodeSystem createCodeSystemWithMoreThan100Concepts() { CodeSystem codeSystem = new CodeSystem(); codeSystem.setUrl(URL_MY_CODE_SYSTEM); @@ -154,19 +91,6 @@ public class TermCodeSystemStorageSvcTest extends BaseJpaR4Test { } - private CodeSystem createSmallCodeSystem() { - CodeSystem codeSystem = new CodeSystem(); - codeSystem.setUrl(URL_MY_CODE_SYSTEM); - - for (int i = 0; i < 10; i++) { - codeSystem.addConcept(new CodeSystem.ConceptDefinitionComponent(new CodeType("codeA " + i))); - } - - codeSystem.setContent(CodeSystem.CodeSystemContentMode.COMPLETE); - return codeSystem; - - } - private void testCreatingAndUpdatingCodeSystemEntity(CodeSystem theUpload, CodeSystem theDuplicate, int expectedCnt, String theDuplicateErrorBaseMsg) { // Create CodeSystem resource diff --git a/hapi-fhir-jpaserver-base/src/test/resources/extensional-case-3-cs-v1.xml b/hapi-fhir-jpaserver-base/src/test/resources/extensional-case-3-cs-v1.xml new file mode 100644 index 00000000000..dc023fe3811 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/resources/extensional-case-3-cs-v1.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hapi-fhir-jpaserver-base/src/test/resources/extensional-case-3-cs-v2.xml b/hapi-fhir-jpaserver-base/src/test/resources/extensional-case-3-cs-v2.xml new file mode 100644 index 00000000000..a9868b624da --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/resources/extensional-case-3-cs-v2.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 3fef8f7befd51e01c8991c4667f2d7a5e6bb929a Mon Sep 17 00:00:00 2001 From: ianmarshall Date: Sun, 30 Aug 2020 15:29:57 -0400 Subject: [PATCH 8/8] Back out changes relating to validate operations. Cleanup of code --- .../dao/r5/FhirResourceDaoCodeSystemR5.java | 2 +- ...aseJpaResourceProviderCodeSystemDstu3.java | 2 +- .../BaseJpaResourceProviderCodeSystemR5.java | 2 +- .../fhir/jpa/term/BaseTermReadSvcImpl.java | 115 ++++++++---------- .../term/TermCodeSystemStorageSvcImpl.java | 15 +-- .../r4/ResourceProviderR4ValueSetTest.java | 45 ------- 6 files changed, 60 insertions(+), 121 deletions(-) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java index 29a2f295eb9..9b5fe473c96 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCodeSystemR5.java @@ -88,7 +88,7 @@ public class FhirResourceDaoCodeSystemR5 extends BaseHapiFhirResourceDao theCode, IPrimitiveType theSystem, Coding theCoding, RequestDetails theRequestDetails) { return lookupCode(theCode, theSystem, theCoding, null, theRequestDetails); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderCodeSystemDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderCodeSystemDstu3.java index 7cc793e5371..6b5c7cd7c77 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderCodeSystemDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/dstu3/BaseJpaResourceProviderCodeSystemDstu3.java @@ -85,7 +85,7 @@ public class BaseJpaResourceProviderCodeSystemDstu3 extends JpaResourceProviderD @OperationParam(name = "system", min = 0, max = 1) UriType theSystem, @OperationParam(name = "codingA", min = 0, max = 1) Coding theCodingA, @OperationParam(name = "codingB", min = 0, max = 1) Coding theCodingB, - @OperationParam(name="version", min=0, max=1) org.hl7.fhir.r4.model.StringType theVersion, + @OperationParam(name="version", min=0, max=1) StringType theVersion, RequestDetails theRequestDetails ) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCodeSystemR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCodeSystemR5.java index bcd7445766f..47c880a6a10 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCodeSystemR5.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r5/BaseJpaResourceProviderCodeSystemR5.java @@ -85,7 +85,7 @@ public class BaseJpaResourceProviderCodeSystemR5 extends JpaResourceProviderR5 theAddedCodes, ValueSet.ConceptSetComponent theIncludeOrExclude, boolean theAdd, AtomicInteger theCodeCounter, int theQueryIndex, VersionIndependentConcept theWantConceptOrNull) { String system = theIncludeOrExclude.getSystem(); - String systemVersion = theIncludeOrExclude.getVersion(); boolean hasSystem = isNotBlank(system); boolean hasValueSet = theIncludeOrExclude.getValueSet().size() > 0; @@ -631,7 +625,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(system); if (cs != null) { - return expandValueSetHandleIncludeOrExcludeUsingDatabase(theValueSetCodeAccumulator, theAddedCodes, theIncludeOrExclude, theAdd, theCodeCounter, theQueryIndex, theWantConceptOrNull, system, cs, systemVersion); + return expandValueSetHandleIncludeOrExcludeUsingDatabase(theValueSetCodeAccumulator, theAddedCodes, theIncludeOrExclude, theAdd, theCodeCounter, theQueryIndex, theWantConceptOrNull, system, cs); } else { @@ -672,7 +666,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { nextSystem = system; } - LookupCodeResult lookup = myValidationSupport.lookupCode(new ValidationSupportContext(provideValidationSupport()), nextSystem, next.getCode(), systemVersion); + LookupCodeResult lookup = myValidationSupport.lookupCode(new ValidationSupportContext(provideValidationSupport()), nextSystem, next.getCode()); if (lookup != null && lookup.isFound()) { addOrRemoveCode(theValueSetCodeAccumulator, theAddedCodes, theAdd, nextSystem, next.getCode(), lookup.getCodeDisplay()); foundCount++; @@ -766,7 +760,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } @Nonnull - private Boolean expandValueSetHandleIncludeOrExcludeUsingDatabase(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set theAddedCodes, ValueSet.ConceptSetComponent theIncludeOrExclude, boolean theAdd, AtomicInteger theCodeCounter, int theQueryIndex, VersionIndependentConcept theWantConceptOrNull, String theSystem, TermCodeSystem theCs, String theSystemVersion) { + private Boolean expandValueSetHandleIncludeOrExcludeUsingDatabase(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set theAddedCodes, ValueSet.ConceptSetComponent theIncludeOrExclude, boolean theAdd, AtomicInteger theCodeCounter, int theQueryIndex, VersionIndependentConcept theWantConceptOrNull, String theSystem, TermCodeSystem theCs) { TermCodeSystemVersion csv = theCs.getCurrentVersion(); FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager); @@ -794,7 +788,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { /* * Filters */ - handleFilters(bool, theSystem, qb, theIncludeOrExclude, theSystemVersion); + handleFilters(bool, theSystem, qb, theIncludeOrExclude); Query luceneQuery = bool.createQuery(); @@ -903,15 +897,15 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } } - private void handleFilters(BooleanJunction theBool, String theSystem, QueryBuilder theQb, ValueSet.ConceptSetComponent theIncludeOrExclude, String theSystemVersion) { + private void handleFilters(BooleanJunction theBool, String theSystem, QueryBuilder theQb, ValueSet.ConceptSetComponent theIncludeOrExclude) { if (theIncludeOrExclude.getFilter().size() > 0) { for (ValueSet.ConceptSetFilterComponent nextFilter : theIncludeOrExclude.getFilter()) { - handleFilter(theSystem, theQb, theBool, nextFilter, theSystemVersion); + handleFilter(theSystem, theQb, theBool, nextFilter); } } } - private void handleFilter(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter, String theSystemVersion) { + private void handleFilter(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { if (isBlank(theFilter.getValue()) && theFilter.getOp() == null && isBlank(theFilter.getProperty())) { return; } @@ -927,24 +921,24 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { break; case "concept": case "code": - handleFilterConceptAndCode(theSystem, theQb, theBool, theFilter, theSystemVersion); + handleFilterConceptAndCode(theSystem, theQb, theBool, theFilter); break; case "parent": case "child": isCodeSystemLoingOrThrowInvalidRequestException(theSystem, theFilter.getProperty()); - handleFilterLoincParentChild(theQb, theBool, theFilter); + handleFilterLoincParentChild(theBool, theFilter); break; case "ancestor": isCodeSystemLoingOrThrowInvalidRequestException(theSystem, theFilter.getProperty()); - handleFilterLoincAncestor(theSystem, theQb, theBool, theFilter, theSystemVersion); + handleFilterLoincAncestor(theSystem, theBool, theFilter); break; case "descendant": isCodeSystemLoingOrThrowInvalidRequestException(theSystem, theFilter.getProperty()); - handleFilterLoincDescendant(theSystem, theQb, theBool, theFilter, theSystemVersion); + handleFilterLoincDescendant(theSystem, theBool, theFilter); break; case "copyright": isCodeSystemLoingOrThrowInvalidRequestException(theSystem, theFilter.getProperty()); - handleFilterLoincCopyright(theQb, theBool, theFilter); + handleFilterLoincCopyright(theBool, theFilter); break; default: handleFilterRegex(theBool, theFilter); @@ -952,11 +946,10 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } } - private boolean isCodeSystemLoingOrThrowInvalidRequestException(String theSystem, String theProperty) { + private void isCodeSystemLoingOrThrowInvalidRequestException(String theSystem, String theProperty) { if (!isCodeSystemLoinc(theSystem)) { throw new InvalidRequestException("Invalid filter, property " + theProperty + " is LOINC-specific and cannot be used with system: " + theSystem); } - return true; } private boolean isCodeSystemLoinc(String theSystem) { @@ -991,8 +984,8 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { bool.must(textQuery); } - private void handleFilterConceptAndCode(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter, String theSystemVersion) { - TermConcept code = findCode(theSystem, theFilter.getValue(), theSystemVersion) + private void handleFilterConceptAndCode(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { + TermConcept code = findCode(theSystem, theFilter.getValue()) .orElseThrow(() -> new InvalidRequestException("Invalid filter criteria - code does not exist: {" + Constants.codeSystemWithDefaultDescription(theSystem) + "}" + theFilter.getValue())); if (theFilter.getOp() == ValueSet.FilterOperator.ISA) { @@ -1004,7 +997,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } @SuppressWarnings("EnumSwitchStatementWhichMissesCases") - private void handleFilterLoincParentChild(QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { + private void handleFilterLoincParentChild(BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { switch (theFilter.getOp()) { case EQUAL: addLoincFilterParentChildEqual(theBool, theFilter.getProperty(), theFilter.getValue()); @@ -1037,41 +1030,41 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } @SuppressWarnings("EnumSwitchStatementWhichMissesCases") - private void handleFilterLoincAncestor(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter, String theSystemVersion) { + private void handleFilterLoincAncestor(String theSystem, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { switch (theFilter.getOp()) { case EQUAL: - addLoincFilterAncestorEqual(theSystem, theQb, theBool, theFilter, theSystemVersion); + addLoincFilterAncestorEqual(theSystem, theBool, theFilter); break; case IN: - addLoincFilterAncestorIn(theSystem, theQb, theBool, theFilter, theSystemVersion); + addLoincFilterAncestorIn(theSystem, theBool, theFilter); break; default: throw new InvalidRequestException("Don't know how to handle op=" + theFilter.getOp() + " on property " + theFilter.getProperty()); } } - private void addLoincFilterAncestorEqual(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter, String theSystemVersion) { - addLoincFilterAncestorEqual(theSystem, theQb, theBool, theFilter.getProperty(), theFilter.getValue(), theSystemVersion); + private void addLoincFilterAncestorEqual(String theSystem, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { + addLoincFilterAncestorEqual(theSystem, theBool, theFilter.getProperty(), theFilter.getValue()); } - private void addLoincFilterAncestorEqual(String theSystem, QueryBuilder theQb, BooleanJunction theBool, String theProperty, String theValue, String theSystemVersion) { - List terms = getAncestorTerms(theSystem, theProperty, theValue, theSystemVersion); + private void addLoincFilterAncestorEqual(String theSystem, BooleanJunction theBool, String theProperty, String theValue) { + List terms = getAncestorTerms(theSystem, theProperty, theValue); theBool.must(new TermsQuery(terms)); } - private void addLoincFilterAncestorIn(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter, String theSystemVersion) { + private void addLoincFilterAncestorIn(String theSystem, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { String[] values = theFilter.getValue().split(","); List terms = new ArrayList<>(); for (String value : values) { - terms.addAll(getAncestorTerms(theSystem, theFilter.getProperty(), value, theSystemVersion)); + terms.addAll(getAncestorTerms(theSystem, theFilter.getProperty(), value)); } theBool.must(new TermsQuery(terms)); } - private List getAncestorTerms(String theSystem, String theProperty, String theValue, String theSystemVersion) { + private List getAncestorTerms(String theSystem, String theProperty, String theValue) { List retVal = new ArrayList<>(); - TermConcept code = findCode(theSystem, theValue, theSystemVersion) + TermConcept code = findCode(theSystem, theValue) .orElseThrow(() -> new InvalidRequestException("Invalid filter criteria - code does not exist: {" + Constants.codeSystemWithDefaultDescription(theSystem) + "}" + theValue)); retVal.add(new Term("myParentPids", "" + code.getId())); @@ -1081,41 +1074,41 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } @SuppressWarnings("EnumSwitchStatementWhichMissesCases") - private void handleFilterLoincDescendant(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter, String theSystemVersion) { + private void handleFilterLoincDescendant(String theSystem, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { switch (theFilter.getOp()) { case EQUAL: - addLoincFilterDescendantEqual(theSystem, theBool, theFilter, theSystemVersion); + addLoincFilterDescendantEqual(theSystem, theBool, theFilter); break; case IN: - addLoincFilterDescendantIn(theSystem, theQb, theBool, theFilter, theSystemVersion); + addLoincFilterDescendantIn(theSystem, theBool, theFilter); break; default: throw new InvalidRequestException("Don't know how to handle op=" + theFilter.getOp() + " on property " + theFilter.getProperty()); } } - private void addLoincFilterDescendantEqual(String theSystem, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter, String theSystemVersion) { - addLoincFilterDescendantEqual(theSystem, theBool, theFilter.getProperty(), theFilter.getValue(), theSystemVersion); + private void addLoincFilterDescendantEqual(String theSystem, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { + addLoincFilterDescendantEqual(theSystem, theBool, theFilter.getProperty(), theFilter.getValue()); } - private void addLoincFilterDescendantEqual(String theSystem, BooleanJunction theBool, String theProperty, String theValue, String theSystemVersion) { - List terms = getDescendantTerms(theSystem, theProperty, theValue, theSystemVersion); + private void addLoincFilterDescendantEqual(String theSystem, BooleanJunction theBool, String theProperty, String theValue) { + List terms = getDescendantTerms(theSystem, theProperty, theValue); theBool.must(new TermsQuery(terms)); } - private void addLoincFilterDescendantIn(String theSystem, QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter, String theSystemVersion) { + private void addLoincFilterDescendantIn(String theSystem, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { String[] values = theFilter.getValue().split(","); List terms = new ArrayList<>(); for (String value : values) { - terms.addAll(getDescendantTerms(theSystem, theFilter.getProperty(), value, theSystemVersion)); + terms.addAll(getDescendantTerms(theSystem, theFilter.getProperty(), value)); } theBool.must(new TermsQuery(terms)); } - private List getDescendantTerms(String theSystem, String theProperty, String theValue, String theSystemVersion) { + private List getDescendantTerms(String theSystem, String theProperty, String theValue) { List retVal = new ArrayList<>(); - TermConcept code = findCode(theSystem, theValue, theSystemVersion) + TermConcept code = findCode(theSystem, theValue) .orElseThrow(() -> new InvalidRequestException("Invalid filter criteria - code does not exist: {" + Constants.codeSystemWithDefaultDescription(theSystem) + "}" + theValue)); String[] parentPids = code.getParentPidsAsString().split(" "); @@ -1127,7 +1120,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { return retVal; } - private void handleFilterLoincCopyright(QueryBuilder theQb, BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { + private void handleFilterLoincCopyright(BooleanJunction theBool, ValueSet.ConceptSetFilterComponent theFilter) { if (theFilter.getOp() == ValueSet.FilterOperator.EQUAL) { String copyrightFilterValue = defaultString(theFilter.getValue()).toLowerCase(); @@ -1365,7 +1358,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { @Override public Optional findCode(String theCodeSystem, String theCode) { return findCode(theCodeSystem, theCode, null); - } @Override @@ -1378,7 +1370,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_MANDATORY); return txTemplate.execute(t -> { - TermCodeSystemVersion csv = getCurrentCodeSystemVersionForVersion(theCodeSystem, theVersion); + TermCodeSystemVersion csv = getCurrentCodeSystemVersion(theCodeSystem, theVersion); if (csv == null) { return Optional.empty(); } @@ -1387,8 +1379,8 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } @Nullable - private TermCodeSystemVersion getCurrentCodeSystemVersionForVersion(String theUri, String theVersion) { - StringBuffer key = new StringBuffer(theUri); + private TermCodeSystemVersion getCurrentCodeSystemVersion(String theUri, String theVersion) { + StringBuilder key = new StringBuilder(theUri); if (theVersion != null) { key.append("_").append(theVersion); } @@ -1820,16 +1812,13 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { @Override public IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, - IPrimitiveType theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB) { + IPrimitiveType theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB) { return subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, null, null, null); } @Override @Transactional - public IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, - IPrimitiveType theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB, - IPrimitiveType theSystemVersion, String theCodingAVersion, - String theCodingBVersion) { + public IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType theCodeA, IPrimitiveType theCodeB, IPrimitiveType theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB, IPrimitiveType theSystemVersion, String theCodingAVersion, String theCodingBVersion) { VersionIndependentConceptWithSystemVersion conceptA = toConcept(theCodeA, theSystem, theCodingA, theSystemVersion, theCodingAVersion); VersionIndependentConceptWithSystemVersion conceptB = toConcept(theCodeB, theSystem, theCodingB, theSystemVersion, theCodingBVersion); @@ -2085,6 +2074,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { while (scrollableResultsIterator.hasNext()) { TermConceptMapGroupElement nextElement = scrollableResultsIterator.next(); + // TODO: The invocation of the size() below does not seem to be necessary but for some reason, removing it causes tests in TerminologySvcImplR4Test to fail. nextElement.getConceptMapGroupElementTargets().size(); myEntityManager.detach(nextElement); @@ -2138,11 +2128,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { @CoverageIgnore @Override public IValidationSupport.CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { - return validateCode(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, theValueSetUrl, null); - } - - @Override - public IValidationSupport.CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl, String theSystemVersion) { invokeRunnableForUnitTest(); if (isNotBlank(theValueSetUrl)) { @@ -2151,7 +2136,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); - Optional codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode, theSystemVersion).map(c -> new VersionIndependentConcept(theCodeSystem, c.getCode()))); + Optional codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> new VersionIndependentConcept(theCodeSystem, c.getCode()))); if (codeOpt != null && codeOpt.isPresent()) { VersionIndependentConcept code = codeOpt.get(); @@ -2182,7 +2167,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } } - CodeValidationResult retVal = null; + CodeValidationResult retVal; if (valueSet != null) { retVal = new InMemoryTerminologyServerValidationSupport(myContext).validateCodeInValueSet(theValidationSupportContext, theValidationOptions, theCodeSystem, theCode, theDisplay, valueSet); } else { @@ -2415,9 +2400,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc { } @NotNull - private VersionIndependentConceptWithSystemVersion toConcept(IPrimitiveType theCodeType, IPrimitiveType theSystemType, - IBaseCoding theCodingType, IPrimitiveType theSystemVersionType, - String theCodingVersionType) { + private VersionIndependentConceptWithSystemVersion toConcept(IPrimitiveType theCodeType, IPrimitiveType theSystemType, IBaseCoding theCodingType, IPrimitiveType theSystemVersionType, String theCodingVersionType) { String code = theCodeType != null ? theCodeType.getValueAsString() : null; String system = theSystemType != null ? theSystemType.getValueAsString() : null; String systemVersion = theSystemVersionType != null ? theSystemVersionType.getValueAsString() : null; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java index 6230a94cc35..598c5eccdd4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermCodeSystemStorageSvcImpl.java @@ -234,10 +234,12 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { @Override @Transactional(propagation = Propagation.NEVER) public void deleteCodeSystemVersion(TermCodeSystemVersion theCodeSystemVersion) { - TermCodeSystem termCodeSystem = theCodeSystemVersion.getCodeSystem(); + // Delete TermCodeSystemVersion ourLog.info(" * Deleting code system version {}", theCodeSystemVersion.getCodeSystemVersionId()); deleteCodeSystemVersion(theCodeSystemVersion.getPid()); + // Check if the version deleted is the current version. If so, delete TermCodeSystem as well. + TermCodeSystem termCodeSystem = theCodeSystemVersion.getCodeSystem(); TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); txTemplate.executeWithoutResult(t -> { @@ -312,8 +314,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { if (termCodeSystem != null) { TermCodeSystemVersion codeSystemVersion = getExistingTermCodeSystemVersion(termCodeSystem.getPid(), theCodeSystem.getVersion()); if (codeSystemVersion != null) { - TermCodeSystem myCodeSystemEntity = getOrCreateTermCodeSystem(codeSystemResourcePid, theCodeSystem.getUrl(), theCodeSystem.getUrl(), theResourceEntity); - validateCodeSystemVersionUpdate(myCodeSystemEntity,theCodeSystem.getUrl(), theCodeSystem.getVersion(), theResourceEntity); + TermCodeSystem myCodeSystemEntity = getOrCreateDistinctTermCodeSystem(codeSystemResourcePid, theCodeSystem.getUrl(), theCodeSystem.getUrl(), theCodeSystem.getVersion(), theResourceEntity); return; } } @@ -370,8 +371,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { /* * Get CodeSystem and validate CodeSystemVersion */ - TermCodeSystem codeSystem = getOrCreateTermCodeSystem(theCodeSystemResourcePid, theSystemUri, theSystemName, theCodeSystemResourceTable); - validateCodeSystemVersionUpdate(codeSystem, theSystemUri, theSystemVersionId, theCodeSystemResourceTable); + TermCodeSystem codeSystem = getOrCreateDistinctTermCodeSystem(theCodeSystemResourcePid, theSystemUri, theSystemName, theSystemVersionId, theCodeSystemResourceTable); /* * Delete version being replaced. @@ -691,7 +691,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { } @Nonnull - private TermCodeSystem getOrCreateTermCodeSystem(ResourcePersistentId theCodeSystemResourcePid, String theSystemUri, String theSystemName, ResourceTable theCodeSystemResourceTable) { + private TermCodeSystem getOrCreateDistinctTermCodeSystem(ResourcePersistentId theCodeSystemResourcePid, String theSystemUri, String theSystemName, String theSystemVersionId, ResourceTable theCodeSystemResourceTable) { TermCodeSystem codeSystem = myCodeSystemDao.findByCodeSystemUri(theSystemUri); if (codeSystem == null) { codeSystem = myCodeSystemDao.findByResourcePid(theCodeSystemResourcePid.getIdAsLong()); @@ -704,10 +704,11 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc { codeSystem.setCodeSystemUri(theSystemUri); codeSystem.setName(theSystemName); codeSystem = myCodeSystemDao.save(codeSystem); + checkForCodeSystemVersionDuplicate(codeSystem,theSystemUri, theSystemVersionId, theCodeSystemResourceTable); return codeSystem; } - private void validateCodeSystemVersionUpdate(TermCodeSystem theCodeSystem, String theSystemUri, String theSystemVersionId, ResourceTable theCodeSystemResourceTable) { + private void checkForCodeSystemVersionDuplicate(TermCodeSystem theCodeSystem, String theSystemUri, String theSystemVersionId, ResourceTable theCodeSystemResourceTable) { // Check if CodeSystemVersion entity already exists. TermCodeSystemVersion codeSystemVersionEntity; String msg = null; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java index 0d2c8d18407..1785024a411 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetTest.java @@ -416,51 +416,6 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test { } - @Test - public void testExpandByValueSetWithFilter() throws IOException { - loadAndPersistCodeSystem(); - - ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); - - Parameters respParam = myClient - .operation() - .onType(ValueSet.class) - .named("expand") - .withParameter(Parameters.class, "valueSet", toExpand) - .andParameter("filter", new StringType("blood")) - .execute(); - ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); - - String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); - ourLog.info(resp); - assertThat(resp, stringContainsInOrder( - "", - "")); - - } - - @Test - public void testExpandByUrlWithFilter() throws Exception { - loadAndPersistCodeSystemAndValueSet(); - - Parameters respParam = myClient - .operation() - .onType(ValueSet.class) - .named("expand") - .withParameter(Parameters.class, "url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2")) - .andParameter("filter", new StringType("first")) - .execute(); - ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); - - String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); - ourLog.info(resp); - assertThat(resp, stringContainsInOrder( - "", - "")); - - } - - @Test public void testExpandByValueSetWithPreExpansion() throws IOException { myDaoConfig.setPreExpandValueSets(true);