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 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) {