Initial changes to support multiple versions for code system.

This commit is contained in:
ianmarshall 2020-08-09 11:49:04 -04:00
parent 63ef2ce006
commit d39348d7bd
35 changed files with 742 additions and 102 deletions

View File

@ -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. <code>Observation.code</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. "<code>http://loinc.org</code>"
* @param theCode The code, e.g. "<code>1234-5</code>"
* @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 <code>true</code> if the given valueset can be validated by the given
* validation support module

View File

@ -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;
}
/**
* <p>
* 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.
* </p>
* <p>
* The default value for this setting is {@code false}.
* </p>
*
* @since 5.1.0
*/
public boolean isMultipleCodeSystemVersionsEnabled() {
return myMultipleCodeSystemVersionsEnabled;
}
/**
* <p>
* 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.
* </p>
* <p>
* The default value for this setting is {@code false}.
* </p>
*
* @since 5.1.0
*/
public void setMultipleCodeSystemVersionsEnabled(Boolean theMultipleCodeSystemVersionsEnabled) {
myMultipleCodeSystemVersionsEnabled = theMultipleCodeSystemVersionsEnabled;
}
}

View File

@ -42,6 +42,11 @@ public interface IFhirResourceDaoCodeSystem<T extends IBaseResource, CD, CC> ext
SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, CD theCodingA, CD theCodingB, RequestDetails theRequestDetails);
@Nonnull
IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, CD theCoding, IPrimitiveType<String> theVersion, RequestDetails theRequestDetails);
SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, CD theCodingA, CD theCodingB, IPrimitiveType<String> theVersion, RequestDetails theRequestDetails);
class SubsumesResult {
private final ConceptSubsumptionOutcome myOutcome;

View File

@ -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<ValueS
return null;
}
@NotNull
@Override
public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, CodingDt theCoding, IPrimitiveType<String> theVersion, RequestDetails theRequestDetails) {
throw new UnsupportedOperationException();
}
@Override
public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, CodingDt theCoding, RequestDetails theRequest) {
boolean haveCoding = theCoding != null && isNotBlank(theCoding.getSystem()) && isNotBlank(theCoding.getCode());
@ -280,6 +287,11 @@ public class FhirResourceDaoValueSetDstu2 extends BaseHapiFhirResourceDao<ValueS
return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB);
}
@Override
public SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, CodingDt theCodingA, CodingDt theCodingB, IPrimitiveType<String> theVersion, RequestDetails theRequestDetails) {
throw new UnsupportedOperationException();
}
@Override
@PostConstruct
public void postConstruct() {

View File

@ -35,9 +35,19 @@ public interface ITermCodeSystemVersionDao extends JpaRepository<TermCodeSystemV
@Query("DELETE FROM TermCodeSystemVersion csv WHERE csv.myCodeSystem = :cs")
void deleteForCodeSystem(@Param("cs") TermCodeSystem theCodeSystem);
@Modifying
@Query("DELETE FROM TermCodeSystemVersion csv WHERE csv.myId = :pid")
void delete(@Param("pid") Long codesystemversion_pid);
@Query("SELECT cs FROM TermCodeSystemVersion cs WHERE cs.myCodeSystemPid = :codesystem_pid")
List<TermCodeSystemVersion> 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<TermCodeSystemVersion> findByCodeSystemResourcePid(@Param("resource_id") Long theCodeSystemResourcePid);

View File

@ -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<Code
return valueSetIds;
}
@Nonnull
@NotNull
@Override
public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, Coding theCoding, RequestDetails theRequestDetails) {
return lookupCode(theCode, theSystem, theCoding, null, theRequestDetails);
}
@Nonnull
@Override
public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, Coding theCoding, IPrimitiveType<String> 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<Code
code = theCode.getValue();
system = theSystem.getValue();
}
String codeSystemVersion = null;
if (theVersion != null) {
codeSystemVersion = theVersion.getValue();
}
ourLog.debug("Looking up {} / {}", system, code);
ourLog.info("Looking up {} / {}, version {}", system, code, codeSystemVersion);
if (myValidationSupport.isCodeSystemSupported(new ValidationSupportContext(myValidationSupport), system)) {
ourLog.debug("Code system {} is supported", system);
IValidationSupport.LookupCodeResult result = myValidationSupport.lookupCode(new ValidationSupportContext(myValidationSupport), system, code);
IValidationSupport.LookupCodeResult result = myValidationSupport.lookupCode(new ValidationSupportContext(myValidationSupport), system, code, codeSystemVersion);
if (result != null) {
return result;
}
@ -124,6 +135,11 @@ public class FhirResourceDaoCodeSystemDstu3 extends BaseHapiFhirResourceDao<Code
return IValidationSupport.LookupCodeResult.notFound(system, code);
}
@Override
public SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, Coding theCodingA, Coding theCodingB, IPrimitiveType<String> theVersion, RequestDetails theRequestDetails) {
return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theVersion);
}
@Override
public SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, Coding theCodingA, Coding theCodingB, RequestDetails theRequestDetails) {
return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB);

View File

@ -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<CodeSys
return valueSetIds;
}
@Nonnull
@NotNull
@Override
public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, Coding theCoding, RequestDetails theRequestDetails) {
return lookupCode(theCode, theSystem, theCoding, null, theRequestDetails);
}
@Nonnull
@Override
public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, Coding theCoding, IPrimitiveType<String> 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<CodeSys
code = theCode.getValue();
system = theSystem.getValue();
}
String codeSystemVersion = null;
if (theVersion != null) {
codeSystemVersion = theVersion.getValue();
}
ourLog.debug("Looking up {} / {}", system, code);
ourLog.info("Looking up {} / {}, version {}", system, code, codeSystemVersion);
if (myValidationSupport.isCodeSystemSupported(new ValidationSupportContext(myValidationSupport), system)) {
ourLog.debug("Code system {} is supported", system);
IValidationSupport.LookupCodeResult retVal = myValidationSupport.lookupCode(new ValidationSupportContext(myValidationSupport), system, code);
IValidationSupport.LookupCodeResult retVal = myValidationSupport.lookupCode(new ValidationSupportContext(myValidationSupport), system, code, codeSystemVersion);
if (retVal != null) {
return retVal;
}
@ -122,6 +133,11 @@ public class FhirResourceDaoCodeSystemR4 extends BaseHapiFhirResourceDao<CodeSys
}
@Override
public SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, Coding theCodingA, Coding theCodingB, IPrimitiveType<String> theVersion, RequestDetails theRequestDetails) {
return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theVersion);
}
@Override
public SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, Coding theCodingA, Coding theCodingB, RequestDetails theRequestDetails) {
return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB);

View File

@ -113,14 +113,17 @@ public class FhirResourceDaoValueSetR4 extends BaseHapiFhirResourceDao<ValueSet>
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>
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);

View File

@ -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<CodeSys
return valueSetIds;
}
@Nonnull
@NotNull
@Override
public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, Coding theCoding, RequestDetails theRequestDetails) {
return lookupCode(theCode, theSystem, theCoding, null, theRequestDetails);
}
@Nonnull
@Override
public IValidationSupport.LookupCodeResult lookupCode(IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, Coding theCoding, IPrimitiveType<String> 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<CodeSys
code = theCode.getValue();
system = theSystem.getValue();
}
String codeSystemVersion = null;
if (theVersion != null) {
codeSystemVersion = theVersion.getValue();
}
ourLog.info("Looking up {} / {}", system, code);
ourLog.info("Looking up {} / {}, version {}", system, code, codeSystemVersion);
if (myValidationSupport.isCodeSystemSupported(new ValidationSupportContext(myValidationSupport), system)) {
ourLog.info("Code system {} is supported", system);
IValidationSupport.LookupCodeResult retVal = myValidationSupport.lookupCode(new ValidationSupportContext(myValidationSupport), system, code);
IValidationSupport.LookupCodeResult retVal = myValidationSupport.lookupCode(new ValidationSupportContext(myValidationSupport), system, code, codeSystemVersion);
if (retVal != null) {
return retVal;
}
@ -124,6 +135,11 @@ public class FhirResourceDaoCodeSystemR5 extends BaseHapiFhirResourceDao<CodeSys
}
@Override
public SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, Coding theCodingA, Coding theCodingB, IPrimitiveType<String> theVersion, RequestDetails theRequestDetails) {
return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theVersion);
}
@Override
public SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, Coding theCodingA, Coding theCodingB, RequestDetails theRequestDetails) {
return myTerminologySvc.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB);

View File

@ -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")

View File

@ -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<CodeType> theProperties,
RequestDetails theRequestDetails
) {
@ -60,7 +61,7 @@ public class BaseJpaResourceProviderCodeSystemDstu3 extends JpaResourceProviderD
startRequest(theServletRequest);
try {
IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> dao = (IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept>) 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) {

View File

@ -55,6 +55,7 @@ public class BaseJpaResourceProviderCodeSystemR4 extends JpaResourceProviderR4<C
@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<CodeType> theProperties,
RequestDetails theRequestDetails
) {
@ -62,7 +63,7 @@ public class BaseJpaResourceProviderCodeSystemR4 extends JpaResourceProviderR4<C
startRequest(theServletRequest);
try {
IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> dao = (IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept>) 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 {

View File

@ -55,6 +55,7 @@ public class BaseJpaResourceProviderCodeSystemR5 extends JpaResourceProviderR5<C
@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) org.hl7.fhir.r4.model.StringType theVersion,
@OperationParam(name = "property", min = 0, max = OperationParam.MAX_UNLIMITED) List<CodeType> theProperties,
RequestDetails theRequestDetails
) {
@ -62,7 +63,7 @@ public class BaseJpaResourceProviderCodeSystemR5 extends JpaResourceProviderR5<C
startRequest(theServletRequest);
try {
IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> dao = (IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept>) 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 {

View File

@ -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<String> 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<String> theAddedCodes, ValueSet.ConceptSetComponent theIncludeOrExclude, boolean theAdd, AtomicInteger theCodeCounter, int theQueryIndex, VersionIndependentConcept theWantConceptOrNull, String theSystem, TermCodeSystem theCs) {
private Boolean expandValueSetHandleIncludeOrExcludeUsingDatabase(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set<String> 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<Term> terms = getAncestorTerms(theSystem, theProperty, theValue);
private void addLoincFilterAncestorEqual(String theSystem, QueryBuilder theQb, BooleanJunction<?> theBool, String theProperty, String theValue, String theSystemVersion) {
List<Term> 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<Term> 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<Term> getAncestorTerms(String theSystem, String theProperty, String theValue) {
private List<Term> getAncestorTerms(String theSystem, String theProperty, String theValue, String theSystemVersion) {
List<Term> 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<Term> terms = getDescendantTerms(theSystem, theProperty, theValue);
private void addLoincFilterDescendantEqual(String theSystem, BooleanJunction<?> theBool, String theProperty, String theValue, String theSystemVersion) {
List<Term> 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<Term> 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<Term> getDescendantTerms(String theSystem, String theProperty, String theValue) {
private List<Term> getDescendantTerms(String theSystem, String theProperty, String theValue, String theSystemVersion) {
List<Term> 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<TermConcept> findCode(String theCodeSystem, String theCode) {
return findCode(theCodeSystem, theCode, null);
}
@Override
public Optional<TermConcept> 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<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB) {
return subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, null);
}
@Override
@Transactional
public IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB, IPrimitiveType<String> 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<TermConcept> codeOpt = findCode(theSystem, theCode);
Optional<TermConcept> 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<VersionIndependentConcept> codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> new VersionIndependentConcept(theCodeSystem, c.getCode())));
Optional<VersionIndependentConcept> codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode, theSystemVersion).map(c -> new VersionIndependentConcept(theCodeSystem, c.getCode())));
if (codeOpt != null && codeOpt.isPresent()) {
VersionIndependentConcept code = codeOpt.get();

View File

@ -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<TermCodeSystemVersion> 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();
});
}

View File

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

View File

@ -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

View File

@ -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

View File

@ -73,6 +73,8 @@ public interface ITermReadSvc extends IValidationSupport {
List<VersionIndependentConcept> expandValueSet(ValueSetExpansionOptions theExpansionOptions, String theValueSet);
Optional<TermConcept> findCode(String theCodeSystem, String theCode, String theVersion);
Optional<TermConcept> findCode(String theCodeSystem, String theCode);
Set<TermConcept> findCodesAbove(Long theCodeSystemResourcePid, Long theCodeSystemResourceVersionPid, String theCode);
@ -103,6 +105,8 @@ public interface ITermReadSvc extends IValidationSupport {
IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB);
IFhirResourceDaoCodeSystem.SubsumesResult subsumes(IPrimitiveType<String> theCodeA, IPrimitiveType<String> theCodeB, IPrimitiveType<String> theSystem, IBaseCoding theCodingA, IBaseCoding theCodingB, IPrimitiveType<String> theSystemVersion);
void preExpandDeferredValueSetsToTerminologyTables();
/**

View File

@ -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"),

View File

@ -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

View File

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

View File

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

View File

@ -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(
"<code value=\"11378-7\"/>",
"<display value=\"Systolic blood pressure at First encounter\"/>"));
}
@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(
"<code value=\"11378-7\"/>",
"<display value=\"Systolic blood pressure at First encounter\"/>"));
}
@Test
public void testExpandByValueSetWithPreExpansion() throws IOException {
myDaoConfig.setPreExpandValueSets(true);

View File

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

View File

@ -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());

View File

@ -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]+$"));
});

View File

@ -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<TermConcept> 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<TermCodeSystemVersion> termCodeSystemVersions = myTermCodeSystemVersionDao.findAll();
assertEquals(termCodeSystemVersions.size(), 1);
TermCodeSystemVersion termCodeSystemVersion_1 = termCodeSystemVersions.get(0);
assertEquals(termCodeSystemVersion_1.getConcepts().size(), 2);
Set<TermConcept> 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<TermCodeSystemVersion> termCodeSystemVersions_updated = myTermCodeSystemVersionDao.findAll();
assertEquals(termCodeSystemVersions_updated.size(), 2);
TermCodeSystemVersion termCodeSystemVersion_2 = termCodeSystemVersions_updated.get(1);
assertEquals(termCodeSystemVersion_2.getConcepts().size(), 3);
Set<TermConcept> 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<TermConcept> 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<TermCodeSystemVersion> termCodeSystemVersions = myTermCodeSystemVersionDao.findAll();
assertEquals(termCodeSystemVersions.size(), 1);
TermCodeSystemVersion termCodeSystemVersion_1 = termCodeSystemVersions.get(0);
assertEquals(termCodeSystemVersion_1.getConcepts().size(), 2);
Set<TermConcept> 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<TermCodeSystemVersion> termCodeSystemVersions_updated = myTermCodeSystemVersionDao.findAll();
assertEquals(termCodeSystemVersions_updated.size(), 1);
TermCodeSystemVersion termCodeSystemVersion_2 = termCodeSystemVersions_updated.get(0);
assertEquals(termCodeSystemVersion_2.getConcepts().size(), 2);
Set<TermConcept> 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());
});
}
}

View File

@ -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

View File

@ -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

View File

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

View File

@ -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;

View File

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

View File

@ -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<VersionIndependentConcept> 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()

View File

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