Fix validation for enumerated ValueSets (#1982)

* Allow code validation against enumerated VS

* Work on validation

* Work on this

* Work on tests

* Work on validation

* Work on tests

* Work on validation

* Test fixes

* Add changelog

* For a change

* Test fixes
This commit is contained in:
James Agnew 2020-07-15 13:38:14 -04:00 committed by GitHub
parent 063bf4237c
commit 077ee02771
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 828 additions and 888 deletions

View File

@ -25,6 +25,9 @@ import org.apache.commons.lang3.builder.ToStringStyle;
public class ConceptValidationOptions { public class ConceptValidationOptions {
private boolean myValidateDisplay;
private boolean myInferSystem;
public boolean isInferSystem() { public boolean isInferSystem() {
return myInferSystem; return myInferSystem;
} }
@ -34,12 +37,19 @@ public class ConceptValidationOptions {
return this; return this;
} }
private boolean myInferSystem;
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
.append("inferSystem", myInferSystem) .append("inferSystem", myInferSystem)
.toString(); .toString();
} }
public boolean isValidateDisplay() {
return myValidateDisplay;
}
public ConceptValidationOptions setValidateDisplay(boolean theValidateDisplay) {
myValidateDisplay = theValidateDisplay;
return this;
}
} }

View File

@ -20,6 +20,8 @@ package ca.uhn.fhir.context.support;
* #L% * #L%
*/ */
import org.thymeleaf.util.Validate;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@ -29,6 +31,7 @@ public class ValidationSupportContext {
private final Set<String> myCurrentlyGeneratingSnapshots = new HashSet<>(); private final Set<String> myCurrentlyGeneratingSnapshots = new HashSet<>();
public ValidationSupportContext(IValidationSupport theRootValidationSupport) { public ValidationSupportContext(IValidationSupport theRootValidationSupport) {
Validate.notNull(theRootValidationSupport, "theRootValidationSupport musty not be null");
myRootValidationSupport = theRootValidationSupport; myRootValidationSupport = theRootValidationSupport;
} }

View File

@ -0,0 +1,7 @@
---
type: add
issue: 1982
title: The validator will now accept codes that are defined in a ValueSet where the valueset contains an enumeration of
codes, and the CodeSystem URL refers to an unknown CodeSystem. This allows successful validation of ValueSets in several
IGs that rely on the existence of grammar based systems.

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.api.dao;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
@ -41,31 +42,6 @@ public interface IFhirResourceDaoValueSet<T extends IBaseResource, CD, CC> exten
void purgeCaches(); void purgeCaches();
ValidateCodeResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, CD theCoding, CC theCodeableConcept, RequestDetails theRequestDetails); IValidationSupport.CodeValidationResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode, IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, CD theCoding, CC theCodeableConcept, RequestDetails theRequestDetails);
class ValidateCodeResult {
private String myDisplay;
private String myMessage;
private boolean myResult;
public ValidateCodeResult(boolean theResult, String theMessage, String theDisplay) {
super();
myResult = theResult;
myMessage = theMessage;
myDisplay = theDisplay;
}
public String getDisplay() {
return myDisplay;
}
public String getMessage() {
return myMessage;
}
public boolean isResult() {
return myResult;
}
}
} }

View File

@ -57,6 +57,8 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import static ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoValueSetDstu3.vsValidateCodeOptions;
import static ca.uhn.fhir.jpa.util.LogicUtil.multiXor;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -291,102 +293,14 @@ public class FhirResourceDaoValueSetDstu2 extends BaseHapiFhirResourceDao<ValueS
// nothing // nothing
} }
private String toStringOrNull(IPrimitiveType<String> thePrimitive) { public static String toStringOrNull(IPrimitiveType<String> thePrimitive) {
return thePrimitive != null ? thePrimitive.getValue() : null; return thePrimitive != null ? thePrimitive.getValue() : null;
} }
@Override @Override
public ValidateCodeResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode, public IValidationSupport.CodeValidationResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode,
IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, CodingDt theCoding, CodeableConceptDt theCodeableConcept, RequestDetails theRequest) { IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, CodingDt theCoding, CodeableConceptDt theCodeableConcept, RequestDetails theRequest) {
List<IIdType> valueSetIds; return myTerminologySvc.validateCode(vsValidateCodeOptions(), theId, toStringOrNull(theValueSetIdentifier), toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
boolean haveCodeableConcept = theCodeableConcept != null && theCodeableConcept.getCoding().size() > 0;
boolean haveCoding = theCoding != null && theCoding.isEmpty() == false;
boolean haveCode = theCode != null && theCode.isEmpty() == false;
if (!haveCodeableConcept && !haveCoding && !haveCode) {
throw new InvalidRequestException("No code, coding, or codeableConcept provided to validate");
}
if (!multiXor(haveCodeableConcept, haveCoding, haveCode)) {
throw new InvalidRequestException("$validate-code can only validate (system AND code) OR (coding) OR (codeableConcept)");
}
boolean haveIdentifierParam = theValueSetIdentifier != null && theValueSetIdentifier.isEmpty() == false;
if (theId != null) {
valueSetIds = Collections.singletonList(theId);
} else if (haveIdentifierParam) {
Set<ResourcePersistentId> ids = searchForIds(new SearchParameterMap(ValueSet.SP_IDENTIFIER, new TokenParam(null, theValueSetIdentifier.getValue())), theRequest);
valueSetIds = new ArrayList<>();
for (ResourcePersistentId next : ids) {
IIdType id = myIdHelperService.translatePidIdToForcedId(myFhirContext, "ValueSet", next);
valueSetIds.add(id);
}
} else {
if (theCode == null || theCode.isEmpty()) {
throw new InvalidRequestException("Either ValueSet ID or ValueSet identifier or system and code must be provided. Unable to validate.");
}
String code = theCode.getValue();
String system = toStringOrNull(theSystem);
valueSetIds = findCodeSystemIdsContainingSystemAndCode(code, system, theRequest);
}
for (IIdType nextId : valueSetIds) {
ValueSet expansion = expand(nextId, null, theRequest);
List<ExpansionContains> contains = expansion.getExpansion().getContains();
ValidateCodeResult result = validateCodeIsInContains(contains, toStringOrNull(theSystem), toStringOrNull(theCode), theCoding, theCodeableConcept);
if (result != null) {
if (theDisplay != null && isNotBlank(theDisplay.getValue()) && isNotBlank(result.getDisplay())) {
if (!theDisplay.getValue().equals(result.getDisplay())) {
return new ValidateCodeResult(false, "Display for code does not match", result.getDisplay());
}
}
return result;
}
}
return new ValidateCodeResult(false, "Code not found", null);
}
private ValidateCodeResult validateCodeIsInContains(List<ExpansionContains> contains, String theSystem, String theCode, CodingDt theCoding,
CodeableConceptDt theCodeableConcept) {
for (ExpansionContains nextCode : contains) {
ValidateCodeResult result = validateCodeIsInContains(nextCode.getContains(), theSystem, theCode, theCoding, theCodeableConcept);
if (result != null) {
return result;
}
String system = nextCode.getSystem();
String code = nextCode.getCode();
if (isNotBlank(theCode)) {
if (theCode.equals(code) && (isBlank(theSystem) || theSystem.equals(system))) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
} else if (theCoding != null) {
if (StringUtils.equals(system, theCoding.getSystem()) && StringUtils.equals(code, theCoding.getCode())) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
} else {
for (CodingDt next : theCodeableConcept.getCoding()) {
if (StringUtils.equals(system, next.getSystem()) && StringUtils.equals(code, next.getCode())) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
}
}
}
return null;
}
private static boolean multiXor(boolean... theValues) {
int count = 0;
for (int i = 0; i < theValues.length; i++) {
if (theValues[i]) {
count++;
}
}
return count == 1;
} }
} }

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.dstu3;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
@ -29,14 +30,12 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.util.LogicUtil;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.ElementUtil; import ca.uhn.fhir.util.ElementUtil;
import org.apache.commons.codec.binary.StringUtils;
import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeableConcept; import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.Coding; import org.hl7.fhir.dstu3.model.Coding;
@ -50,15 +49,14 @@ import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import static ca.uhn.fhir.jpa.dao.FhirResourceDaoValueSetDstu2.toStringOrNull;
import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoValueSetR4.validateHaveExpansionOrThrowInternalErrorException; import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoValueSetR4.validateHaveExpansionOrThrowInternalErrorException;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -81,7 +79,7 @@ public class FhirResourceDaoValueSetDstu3 extends BaseHapiFhirResourceDao<ValueS
@Override @Override
public void start() { public void start() {
super.start(); super.start();
myValidationSupport = getApplicationContext().getBean(IValidationSupport.class,"myJpaValidationSupportChain" ); myValidationSupport = getApplicationContext().getBean(IValidationSupport.class, "myJpaValidationSupportChain");
} }
@Override @Override
@ -264,108 +262,10 @@ public class FhirResourceDaoValueSetDstu3 extends BaseHapiFhirResourceDao<ValueS
} }
@Override @Override
public IFhirResourceDaoValueSet.ValidateCodeResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode, public IValidationSupport.CodeValidationResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode,
IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, Coding theCoding, IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, Coding theCoding,
CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) { CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) {
return myTerminologySvc.validateCode(vsValidateCodeOptions(), theId, toStringOrNull(theValueSetIdentifier), toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
List<IIdType> valueSetIds = Collections.emptyList();
boolean haveCodeableConcept = theCodeableConcept != null && theCodeableConcept.getCoding().size() > 0;
boolean haveCoding = theCoding != null && theCoding.isEmpty() == false;
boolean haveCode = theCode != null && theCode.isEmpty() == false;
if (!haveCodeableConcept && !haveCoding && !haveCode) {
throw new InvalidRequestException("No code, coding, or codeableConcept provided to validate");
}
if (!LogicUtil.multiXor(haveCodeableConcept, haveCoding, haveCode)) {
throw new InvalidRequestException("$validate-code can only validate (system AND code) OR (coding) OR (codeableConcept)");
}
boolean haveIdentifierParam = theValueSetIdentifier != null && theValueSetIdentifier.isEmpty() == false;
ValueSet vs = null;
boolean isBuiltInValueSet = false;
if (theId != null) {
vs = read(theId, theRequestDetails);
} else if (haveIdentifierParam) {
vs = (ValueSet) myDefaultProfileValidationSupport.fetchValueSet(theValueSetIdentifier.getValue());
if (vs == null) {
vs = (ValueSet) myValidationSupport.fetchValueSet(theValueSetIdentifier.getValue());
if (vs == null) {
throw new InvalidRequestException("Unknown ValueSet identifier: " + theValueSetIdentifier.getValue());
}
} else {
isBuiltInValueSet = true;
}
} else {
if (theCode == null || theCode.isEmpty()) {
throw new InvalidRequestException("Either ValueSet ID or ValueSet identifier or system and code must be provided. Unable to validate.");
}
// String code = theCode.getValue();
// String system = toStringOrNull(theSystem);
IValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(theCode, theSystem, null, null);
if (result != null && result.isFound()) {
IFhirResourceDaoValueSet.ValidateCodeResult retVal = new ValidateCodeResult(true, "Found code", result.getCodeDisplay());
return retVal;
}
}
if (vs != null) {
ValidateCodeResult result;
if (myDaoConfig.isPreExpandValueSets() && !isBuiltInValueSet && myTerminologySvc.isValueSetPreExpandedForCodeValidation(vs)) {
result = myTerminologySvc.validateCodeIsInPreExpandedValueSet(new ValidationOptions(), vs, toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
} else {
ValueSet expansion = doExpand(vs);
List<ValueSetExpansionContainsComponent> contains = expansion.getExpansion().getContains();
result = validateCodeIsInContains(contains, toStringOrNull(theSystem), toStringOrNull(theCode), theCoding, theCodeableConcept);
}
if (result != null) {
if (theDisplay != null && isNotBlank(theDisplay.getValue()) && isNotBlank(result.getDisplay())) {
if (!theDisplay.getValue().equals(result.getDisplay())) {
return new ValidateCodeResult(false, "Display for code does not match", result.getDisplay());
}
}
return result;
}
}
return new ValidateCodeResult(false, "Code not found", null);
}
private String toStringOrNull(IPrimitiveType<String> thePrimitive) {
return thePrimitive != null ? thePrimitive.getValue() : null;
}
private IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInContains(List<ValueSetExpansionContainsComponent> contains, String theSystem, String theCode,
Coding theCoding, CodeableConcept theCodeableConcept) {
for (ValueSetExpansionContainsComponent nextCode : contains) {
IFhirResourceDaoValueSet.ValidateCodeResult result = validateCodeIsInContains(nextCode.getContains(), theSystem, theCode, theCoding, theCodeableConcept);
if (result != null) {
return result;
}
String system = nextCode.getSystem();
String code = nextCode.getCode();
if (isNotBlank(theCode)) {
if (theCode.equals(code) && (isBlank(theSystem) || theSystem.equals(system))) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
} else if (theCoding != null) {
if (StringUtils.equals(system, theCoding.getSystem()) && StringUtils.equals(code, theCoding.getCode())) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
} else {
for (Coding next : theCodeableConcept.getCoding()) {
if (StringUtils.equals(system, next.getSystem()) && StringUtils.equals(code, next.getCode())) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
}
}
}
return null;
} }
@Override @Override
@ -395,4 +295,8 @@ public class FhirResourceDaoValueSetDstu3 extends BaseHapiFhirResourceDao<ValueS
return retVal; return retVal;
} }
public static ConceptValidationOptions vsValidateCodeOptions() {
return new ConceptValidationOptions().setValidateDisplay(true);
}
} }

View File

@ -31,7 +31,6 @@ import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.util.LogicUtil;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -49,13 +48,13 @@ import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r4.model.ValueSet.ConceptSetFilterComponent; import org.hl7.fhir.r4.model.ValueSet.ConceptSetFilterComponent;
import org.hl7.fhir.r4.model.ValueSet.FilterOperator; import org.hl7.fhir.r4.model.ValueSet.FilterOperator;
import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import static ca.uhn.fhir.jpa.dao.FhirResourceDaoValueSetDstu2.toStringOrNull;
import static ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoValueSetDstu3.vsValidateCodeOptions;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -243,108 +242,11 @@ public class FhirResourceDaoValueSetR4 extends BaseHapiFhirResourceDao<ValueSet>
} }
@Override @Override
public ValidateCodeResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode, public IValidationSupport.CodeValidationResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode,
IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, Coding theCoding, IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, Coding theCoding,
CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) { CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) {
List<IIdType> valueSetIds = Collections.emptyList(); return myTerminologySvc.validateCode(vsValidateCodeOptions(), theId, toStringOrNull(theValueSetIdentifier), toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
boolean haveCodeableConcept = theCodeableConcept != null && theCodeableConcept.getCoding().size() > 0;
boolean haveCoding = theCoding != null && !theCoding.isEmpty();
boolean haveCode = theCode != null && !theCode.isEmpty();
if (!haveCodeableConcept && !haveCoding && !haveCode) {
throw new InvalidRequestException("No code, coding, or codeableConcept provided to validate");
}
if (!LogicUtil.multiXor(haveCodeableConcept, haveCoding, haveCode)) {
throw new InvalidRequestException("$validate-code can only validate (system AND code) OR (coding) OR (codeableConcept)");
}
boolean haveIdentifierParam = theValueSetIdentifier != null && !theValueSetIdentifier.isEmpty();
ValueSet vs = null;
boolean isBuiltInValueSet = false;
if (theId != null) {
vs = read(theId, theRequestDetails);
} else if (haveIdentifierParam) {
vs = (ValueSet) myDefaultProfileValidationSupport.fetchValueSet(theValueSetIdentifier.getValue());
if (vs == null) {
vs = (ValueSet) myValidationSupport.fetchValueSet(theValueSetIdentifier.getValue());
if (vs == null) {
throw new InvalidRequestException("Unknown ValueSet identifier: " + theValueSetIdentifier.getValue());
}
} else {
isBuiltInValueSet = true;
}
} else {
if (theCode == null || theCode.isEmpty()) {
throw new InvalidRequestException("Either ValueSet ID or ValueSet identifier or system and code must be provided. Unable to validate.");
}
// String code = theCode.getValue();
// String system = toStringOrNull(theSystem);
IValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(theCode, theSystem, null, null);
if (result.isFound()) {
ValidateCodeResult retVal = new ValidateCodeResult(true, "Found code", result.getCodeDisplay());
return retVal;
}
}
if (vs != null) {
ValidateCodeResult result;
if (myDaoConfig.isPreExpandValueSets() && !isBuiltInValueSet && myTerminologySvc.isValueSetPreExpandedForCodeValidation(vs)) {
result = myTerminologySvc.validateCodeIsInPreExpandedValueSet(new ValidationOptions(), vs, toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
} else {
ValueSet expansion = doExpand(vs);
List<ValueSetExpansionContainsComponent> contains = expansion.getExpansion().getContains();
result = validateCodeIsInContains(contains, toStringOrNull(theSystem), toStringOrNull(theCode), theCoding, theCodeableConcept);
}
if (result != null) {
if (theDisplay != null && isNotBlank(theDisplay.getValue()) && isNotBlank(result.getDisplay())) {
if (!theDisplay.getValue().equals(result.getDisplay())) {
return new ValidateCodeResult(false, "Display for code does not match", result.getDisplay());
}
}
return result;
}
}
return new ValidateCodeResult(false, "Code not found", null);
}
private String toStringOrNull(IPrimitiveType<String> thePrimitive) {
return thePrimitive != null ? thePrimitive.getValue() : null;
}
private ValidateCodeResult validateCodeIsInContains(List<ValueSetExpansionContainsComponent> contains, String theSystem, String theCode,
Coding theCoding, CodeableConcept theCodeableConcept) {
for (ValueSetExpansionContainsComponent nextCode : contains) {
ValidateCodeResult result = validateCodeIsInContains(nextCode.getContains(), theSystem, theCode, theCoding, theCodeableConcept);
if (result != null) {
return result;
}
String system = nextCode.getSystem();
String code = nextCode.getCode();
if (isNotBlank(theCode)) {
if (theCode.equals(code) && (isBlank(theSystem) || theSystem.equals(system))) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
} else if (theCoding != null) {
if (StringUtils.equals(system, theCoding.getSystem()) && StringUtils.equals(code, theCoding.getCode())) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
} else {
for (Coding next : theCodeableConcept.getCoding()) {
if (StringUtils.equals(system, next.getSystem()) && StringUtils.equals(code, next.getCode())) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
}
}
}
return null;
} }
@Override @Override

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.r5;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
@ -55,6 +56,8 @@ import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import static ca.uhn.fhir.jpa.dao.FhirResourceDaoValueSetDstu2.toStringOrNull;
import static ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoValueSetDstu3.vsValidateCodeOptions;
import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoValueSetR4.validateHaveExpansionOrThrowInternalErrorException; import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoValueSetR4.validateHaveExpansionOrThrowInternalErrorException;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -245,108 +248,10 @@ public class FhirResourceDaoValueSetR5 extends BaseHapiFhirResourceDao<ValueSet>
} }
@Override @Override
public ValidateCodeResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode, public IValidationSupport.CodeValidationResult validateCode(IPrimitiveType<String> theValueSetIdentifier, IIdType theId, IPrimitiveType<String> theCode,
IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, Coding theCoding, IPrimitiveType<String> theSystem, IPrimitiveType<String> theDisplay, Coding theCoding,
CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) { CodeableConcept theCodeableConcept, RequestDetails theRequestDetails) {
return myTerminologySvc.validateCode(vsValidateCodeOptions(), theId, toStringOrNull(theValueSetIdentifier), toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
List<IIdType> valueSetIds = Collections.emptyList();
boolean haveCodeableConcept = theCodeableConcept != null && theCodeableConcept.getCoding().size() > 0;
boolean haveCoding = theCoding != null && theCoding.isEmpty() == false;
boolean haveCode = theCode != null && theCode.isEmpty() == false;
if (!haveCodeableConcept && !haveCoding && !haveCode) {
throw new InvalidRequestException("No code, coding, or codeableConcept provided to validate");
}
if (!LogicUtil.multiXor(haveCodeableConcept, haveCoding, haveCode)) {
throw new InvalidRequestException("$validate-code can only validate (system AND code) OR (coding) OR (codeableConcept)");
}
boolean haveIdentifierParam = theValueSetIdentifier != null && theValueSetIdentifier.isEmpty() == false;
ValueSet vs = null;
boolean isBuiltInValueSet = false;
if (theId != null) {
vs = read(theId, theRequestDetails);
} else if (haveIdentifierParam) {
vs = (ValueSet) myDefaultProfileValidationSupport.fetchValueSet(theValueSetIdentifier.getValue());
if (vs == null) {
vs = (ValueSet) myValidationSupport.fetchValueSet(theValueSetIdentifier.getValue());
if (vs == null) {
throw new InvalidRequestException("Unknown ValueSet identifier: " + theValueSetIdentifier.getValue());
}
} else {
isBuiltInValueSet = true;
}
} else {
if (theCode == null || theCode.isEmpty()) {
throw new InvalidRequestException("Either ValueSet ID or ValueSet identifier or system and code must be provided. Unable to validate.");
}
// String code = theCode.getValue();
// String system = toStringOrNull(theSystem);
IValidationSupport.LookupCodeResult result = myCodeSystemDao.lookupCode(theCode, theSystem, null, null);
if (result.isFound()) {
ValidateCodeResult retVal = new ValidateCodeResult(true, "Found code", result.getCodeDisplay());
return retVal;
}
}
if (vs != null) {
ValidateCodeResult result;
if (myDaoConfig.isPreExpandValueSets() && !isBuiltInValueSet && myTerminologySvc.isValueSetPreExpandedForCodeValidation(vs)) {
result = myTerminologySvc.validateCodeIsInPreExpandedValueSet(new ValidationOptions(), vs, toStringOrNull(theSystem), toStringOrNull(theCode), toStringOrNull(theDisplay), theCoding, theCodeableConcept);
} else {
ValueSet expansion = doExpand(vs);
List<ValueSetExpansionContainsComponent> contains = expansion.getExpansion().getContains();
result = validateCodeIsInContains(contains, toStringOrNull(theSystem), toStringOrNull(theCode), theCoding, theCodeableConcept);
}
if (result != null) {
if (theDisplay != null && isNotBlank(theDisplay.getValue()) && isNotBlank(result.getDisplay())) {
if (!theDisplay.getValue().equals(result.getDisplay())) {
return new ValidateCodeResult(false, "Display for code does not match", result.getDisplay());
}
}
return result;
}
}
return new ValidateCodeResult(false, "Code not found", null);
}
private String toStringOrNull(IPrimitiveType<String> thePrimitive) {
return thePrimitive != null ? thePrimitive.getValue() : null;
}
private ValidateCodeResult validateCodeIsInContains(List<ValueSetExpansionContainsComponent> contains, String theSystem, String theCode,
Coding theCoding, CodeableConcept theCodeableConcept) {
for (ValueSetExpansionContainsComponent nextCode : contains) {
ValidateCodeResult result = validateCodeIsInContains(nextCode.getContains(), theSystem, theCode, theCoding, theCodeableConcept);
if (result != null) {
return result;
}
String system = nextCode.getSystem();
String code = nextCode.getCode();
if (isNotBlank(theCode)) {
if (theCode.equals(code) && (isBlank(theSystem) || theSystem.equals(system))) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
} else if (theCoding != null) {
if (StringUtils.equals(system, theCoding.getSystem()) && StringUtils.equals(code, theCoding.getCode())) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
} else {
for (Coding next : theCodeableConcept.getCoding()) {
if (StringUtils.equals(system, next.getSystem()) && StringUtils.equals(code, next.getCode())) {
return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
}
}
}
}
return null;
} }
@Override @Override

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.provider;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
@ -39,6 +40,8 @@ import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.ParametersUtil;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -142,21 +145,27 @@ public class BaseJpaResourceProviderValueSetDstu2 extends JpaResourceProviderDst
startRequest(theServletRequest); startRequest(theServletRequest);
try { try {
IFhirResourceDaoValueSet<ValueSet, CodingDt, CodeableConceptDt> dao = (IFhirResourceDaoValueSet<ValueSet, CodingDt, CodeableConceptDt>) getDao(); IFhirResourceDaoValueSet<ValueSet, CodingDt, CodeableConceptDt> dao = (IFhirResourceDaoValueSet<ValueSet, CodingDt, CodeableConceptDt>) getDao();
IFhirResourceDaoValueSet.ValidateCodeResult result = dao.validateCode(theValueSetIdentifier, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails); IValidationSupport.CodeValidationResult result = dao.validateCode(theValueSetIdentifier, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
Parameters retVal = new Parameters(); return (Parameters) toValidateCodeResult(getContext(), result);
retVal.addParameter().setName("result").setValue(new BooleanDt(result.isResult()));
if (isNotBlank(result.getMessage())) {
retVal.addParameter().setName("message").setValue(new StringDt(result.getMessage()));
}
if (isNotBlank(result.getDisplay())) {
retVal.addParameter().setName("display").setValue(new StringDt(result.getDisplay()));
}
return retVal;
} finally { } finally {
endRequest(theServletRequest); endRequest(theServletRequest);
} }
} }
public static IBaseParameters toValidateCodeResult(FhirContext theContext, IValidationSupport.CodeValidationResult theResult) {
IBaseParameters retVal = ParametersUtil.newInstance(theContext);
ParametersUtil.addParameterToParametersBoolean(theContext, retVal, "result", theResult.isOk());
if (isNotBlank(theResult.getMessage())) {
ParametersUtil.addParameterToParametersString(theContext, retVal, "message", theResult.getMessage());
}
if (isNotBlank(theResult.getDisplay())) {
ParametersUtil.addParameterToParametersString(theContext, retVal, "display", theResult.getDisplay());
}
return retVal;
}
private static boolean moreThanOneTrue(boolean... theBooleans) { private static boolean moreThanOneTrue(boolean... theBooleans) {
boolean haveOne = false; boolean haveOne = false;
for (boolean next : theBooleans) { for (boolean next : theBooleans) {

View File

@ -20,8 +20,10 @@ package ca.uhn.fhir.jpa.provider.dstu3;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.BaseJpaResourceProviderValueSetDstu2;
import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.annotation.OperationParam;
@ -148,16 +150,8 @@ public class BaseJpaResourceProviderValueSetDstu3 extends JpaResourceProviderDst
startRequest(theServletRequest); startRequest(theServletRequest);
try { try {
IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao(); IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao();
IFhirResourceDaoValueSet.ValidateCodeResult result = dao.validateCode(url, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails); IValidationSupport.CodeValidationResult result = dao.validateCode(url, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
Parameters retVal = new Parameters(); return (Parameters) BaseJpaResourceProviderValueSetDstu2.toValidateCodeResult(getContext(), result);
retVal.addParameter().setName("result").setValue(new BooleanType(result.isResult()));
if (isNotBlank(result.getMessage())) {
retVal.addParameter().setName("message").setValue(new StringType(result.getMessage()));
}
if (isNotBlank(result.getDisplay())) {
retVal.addParameter().setName("display").setValue(new StringType(result.getDisplay()));
}
return retVal;
} finally { } finally {
endRequest(theServletRequest); endRequest(theServletRequest);
} }

View File

@ -20,8 +20,10 @@ package ca.uhn.fhir.jpa.provider.r4;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.BaseJpaResourceProviderValueSetDstu2;
import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.annotation.OperationParam;
@ -134,16 +136,8 @@ public class BaseJpaResourceProviderValueSetR4 extends JpaResourceProviderR4<Val
startRequest(theServletRequest); startRequest(theServletRequest);
try { try {
IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao(); IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao();
IFhirResourceDaoValueSet.ValidateCodeResult result = dao.validateCode(theValueSetUrl, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails); IValidationSupport.CodeValidationResult result = dao.validateCode(theValueSetUrl, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
Parameters retVal = new Parameters(); return (Parameters) BaseJpaResourceProviderValueSetDstu2.toValidateCodeResult(getContext(), result);
retVal.addParameter().setName("result").setValue(new BooleanType(result.isResult()));
if (isNotBlank(result.getMessage())) {
retVal.addParameter().setName("message").setValue(new StringType(result.getMessage()));
}
if (isNotBlank(result.getDisplay())) {
retVal.addParameter().setName("display").setValue(new StringType(result.getDisplay()));
}
return retVal;
} finally { } finally {
endRequest(theServletRequest); endRequest(theServletRequest);
} }

View File

@ -20,8 +20,10 @@ package ca.uhn.fhir.jpa.provider.r5;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.BaseJpaResourceProviderValueSetDstu2;
import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.annotation.OperationParam;
@ -134,16 +136,8 @@ public class BaseJpaResourceProviderValueSetR5 extends JpaResourceProviderR5<Val
startRequest(theServletRequest); startRequest(theServletRequest);
try { try {
IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao(); IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao();
IFhirResourceDaoValueSet.ValidateCodeResult result = dao.validateCode(theValueSetUrl, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails); IValidationSupport.CodeValidationResult result = dao.validateCode(theValueSetUrl, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
Parameters retVal = new Parameters(); return (Parameters) BaseJpaResourceProviderValueSetDstu2.toValidateCodeResult(getContext(), result);
retVal.addParameter().setName("result").setValue(new BooleanType(result.isResult()));
if (isNotBlank(result.getMessage())) {
retVal.addParameter().setName("message").setValue(new StringType(result.getMessage()));
}
if (isNotBlank(result.getDisplay())) {
retVal.addParameter().setName("display").setValue(new StringType(result.getDisplay()));
}
return retVal;
} finally { } finally {
endRequest(theServletRequest); endRequest(theServletRequest);
} }

View File

@ -30,7 +30,6 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IDao; import ca.uhn.fhir.jpa.api.dao.IDao;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.api.model.TranslationQuery; import ca.uhn.fhir.jpa.api.model.TranslationQuery;
import ca.uhn.fhir.jpa.api.model.TranslationRequest; import ca.uhn.fhir.jpa.api.model.TranslationRequest;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
@ -73,6 +72,7 @@ import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException; import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException;
import ca.uhn.fhir.jpa.util.LogicUtil;
import ca.uhn.fhir.jpa.util.ScrollableResultsIterator; import ca.uhn.fhir.jpa.util.ScrollableResultsIterator;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
@ -81,6 +81,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.util.ValidateUtil; import ca.uhn.fhir.util.ValidateUtil;
@ -105,10 +106,14 @@ import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.FullTextQuery; import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.query.dsl.BooleanJunction; import org.hibernate.search.query.dsl.BooleanJunction;
import org.hibernate.search.query.dsl.QueryBuilder; import org.hibernate.search.query.dsl.QueryBuilder;
import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService;
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseCoding; import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseResource; 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.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem;
@ -169,6 +174,7 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNoneBlank; import static org.apache.commons.lang3.StringUtils.isNoneBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
public abstract class BaseTermReadSvcImpl implements ITermReadSvc { public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
public static final int DEFAULT_FETCH_SIZE = 250; public static final int DEFAULT_FETCH_SIZE = 250;
@ -653,7 +659,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
includedConcepts = theIncludeOrExclude includedConcepts = theIncludeOrExclude
.getConcept() .getConcept()
.stream() .stream()
.map(t->new VersionIndependentConcept(theIncludeOrExclude.getSystem(), t.getCode())) .map(t -> new VersionIndependentConcept(theIncludeOrExclude.getSystem(), t.getCode()))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@ -665,7 +671,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
nextSystem = system; nextSystem = system;
} }
LookupCodeResult lookup = myValidationSupport.lookupCode(new ValidationSupportContext(myValidationSupport), nextSystem, next.getCode()); LookupCodeResult lookup = myValidationSupport.lookupCode(new ValidationSupportContext(provideValidationSupport()), nextSystem, next.getCode());
if (lookup != null && lookup.isFound()) { if (lookup != null && lookup.isFound()) {
addOrRemoveCode(theValueSetCodeAccumulator, theAddedCodes, theAdd, nextSystem, next.getCode(), lookup.getCodeDisplay()); addOrRemoveCode(theValueSetCodeAccumulator, theAddedCodes, theAdd, nextSystem, next.getCode(), lookup.getCodeDisplay());
foundCount++; foundCount++;
@ -1249,8 +1255,8 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
return true; return true;
} }
protected IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInPreExpandedValueSet( protected IValidationSupport.CodeValidationResult validateCodeIsInPreExpandedValueSet(
ValidationOptions theValidationOptions, ConceptValidationOptions theValidationOptions,
ValueSet theValueSet, String theSystem, String theCode, String theDisplay, Coding theCoding, CodeableConcept theCodeableConcept) { ValueSet theValueSet, String theSystem, String theCode, String theDisplay, Coding theCoding, CodeableConcept theCodeableConcept) {
ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet.hasId(), "ValueSet.id is required"); ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet.hasId(), "ValueSet.id is required");
@ -1258,7 +1264,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
List<TermValueSetConcept> concepts = new ArrayList<>(); List<TermValueSetConcept> concepts = new ArrayList<>();
if (isNotBlank(theCode)) { if (isNotBlank(theCode)) {
if (theValidationOptions.isGuessSystem()) { if (theValidationOptions.isInferSystem()) {
concepts.addAll(myValueSetConceptDao.findByValueSetResourcePidAndCode(valueSetResourcePid.getIdAsLong(), theCode)); concepts.addAll(myValueSetConceptDao.findByValueSetResourcePidAndCode(valueSetResourcePid.getIdAsLong(), theCode));
} else if (isNotBlank(theSystem)) { } else if (isNotBlank(theSystem)) {
concepts.addAll(findByValueSetResourcePidSystemAndCode(valueSetResourcePid, theSystem, theCode)); concepts.addAll(findByValueSetResourcePidSystemAndCode(valueSetResourcePid, theSystem, theCode));
@ -1276,19 +1282,40 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
} }
} }
} }
} else {
return null;
} }
for (TermValueSetConcept concept : concepts) { if (theValidationOptions.isValidateDisplay() && concepts.size() > 0) {
if (isNotBlank(theDisplay) && theDisplay.equals(concept.getDisplay())) { for (TermValueSetConcept concept : concepts) {
return new IFhirResourceDaoValueSet.ValidateCodeResult(true, "Validation succeeded", concept.getDisplay()); if (isBlank(theDisplay) || isBlank(concept.getDisplay()) || theDisplay.equals(concept.getDisplay())) {
return new IValidationSupport.CodeValidationResult()
.setCode(concept.getCode())
.setDisplay(concept.getDisplay());
}
} }
return createFailureCodeValidationResult(theSystem, theCode, " - Concept Display \"" + theDisplay + "\" does not match expected \"" + concepts.get(0).getDisplay() + "\"").setDisplay(concepts.get(0).getDisplay());
} }
if (!concepts.isEmpty()) { if (!concepts.isEmpty()) {
return new IFhirResourceDaoValueSet.ValidateCodeResult(true, "Validation succeeded", concepts.get(0).getDisplay()); return new IValidationSupport.CodeValidationResult()
.setCode(concepts.get(0).getCode())
.setDisplay(concepts.get(0).getDisplay());
} }
return null; return createFailureCodeValidationResult(theSystem, theCode);
}
private CodeValidationResult createFailureCodeValidationResult(String theSystem, String theCode) {
String append = "";
return createFailureCodeValidationResult(theSystem, theCode, append);
}
private CodeValidationResult createFailureCodeValidationResult(String theSystem, String theCode, String theAppend) {
return new CodeValidationResult()
.setSeverity(IssueSeverity.ERROR)
.setMessage("Unknown code {" + theSystem + "}" + theCode + theAppend);
} }
private List<TermValueSetConcept> findByValueSetResourcePidSystemAndCode(ResourcePersistentId theResourcePid, String theSystem, String theCode) { private List<TermValueSetConcept> findByValueSetResourcePidSystemAndCode(ResourcePersistentId theResourcePid, String theSystem, String theCode) {
@ -1659,6 +1686,58 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
} }
} }
@Override
public CodeValidationResult validateCode(ConceptValidationOptions theOptions, IIdType theValueSetId, String theValueSetUrl, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) {
CodeableConcept codeableConcept = toCanonicalCodeableConcept(theCodeableConcept);
boolean haveCodeableConcept = codeableConcept != null && codeableConcept.getCoding().size() > 0;
Coding coding = toCanonicalCoding(theCoding);
boolean haveCoding = coding != null && coding.isEmpty() == false;
boolean haveCode = theCode != null && theCode.isEmpty() == false;
if (!haveCodeableConcept && !haveCoding && !haveCode) {
throw new InvalidRequestException("No code, coding, or codeableConcept provided to validate");
}
if (!LogicUtil.multiXor(haveCodeableConcept, haveCoding, haveCode)) {
throw new InvalidRequestException("$validate-code can only validate (system AND code) OR (coding) OR (codeableConcept)");
}
boolean haveIdentifierParam = isNotBlank(theValueSetUrl);
String valueSetUrl;
if (theValueSetId != null) {
IBaseResource valueSet = myDaoRegistry.getResourceDao("ValueSet").read(theValueSetId);
valueSetUrl = CommonCodeSystemsTerminologyService.getValueSetUrl(valueSet);
} else if (haveIdentifierParam) {
valueSetUrl = theValueSetUrl;
} else {
throw new InvalidRequestException("Either ValueSet ID or ValueSet identifier or system and code must be provided. Unable to validate.");
}
ValidationSupportContext validationContext = new ValidationSupportContext(provideValidationSupport());
String code = theCode;
String system = theSystem;
String display = theDisplay;
if (haveCodeableConcept) {
for (int i = 0; i < codeableConcept.getCoding().size(); i++) {
Coding nextCoding = codeableConcept.getCoding().get(i);
CodeValidationResult nextValidation = validateCode(validationContext, theOptions, nextCoding.getSystem(), nextCoding.getCode(), nextCoding.getDisplay(), valueSetUrl);
if (nextValidation.isOk() || i == codeableConcept.getCoding().size() - 1) {
return nextValidation;
}
}
} else if (haveCoding) {
system = coding.getSystem();
code = coding.getCode();
display = coding.getDisplay();
}
return validateCode(validationContext, theOptions, system, code, display, valueSetUrl);
}
private boolean isNotSafeToPreExpandValueSets() { private boolean isNotSafeToPreExpandValueSets() {
return myDeferredStorageSvc != null && !myDeferredStorageSvc.isStorageQueueEmpty(); return myDeferredStorageSvc != null && !myDeferredStorageSvc.isStorageQueueEmpty();
} }
@ -2020,7 +2099,36 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
return null; return null;
} }
Optional<VersionIndependentConcept> validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theValidationOptions, String theValueSetUrl, String theCodeSystem, String theCode) {
@CoverageIgnore
@Override
public IValidationSupport.CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
invokeRunnableForUnitTest();
if (isNotBlank(theValueSetUrl)) {
return validateCodeInValueSet(theValidationSupportContext, theOptions, theValueSetUrl, theCodeSystem, theCode, theDisplay);
}
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())));
if (codeOpt != null && codeOpt.isPresent()) {
VersionIndependentConcept code = codeOpt.get();
if (!theOptions.isValidateDisplay() || (isNotBlank(code.getDisplay()) && isNotBlank(theDisplay) && code.getDisplay().equals(theDisplay))) {
return new CodeValidationResult()
.setCode(code.getCode())
.setDisplay(code.getDisplay());
} else {
return createFailureCodeValidationResult(theCodeSystem, theCode, " - Concept Display \"" + code.getDisplay() + "\" does not match expected \"" + code.getDisplay() + "\"").setDisplay(code.getDisplay());
}
}
return createFailureCodeValidationResult(theCodeSystem, theCode);
}
IValidationSupport.CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theValidationOptions, String theValueSetUrl, String theCodeSystem, String theCode, String theDisplay) {
IBaseResource valueSet = theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl); IBaseResource valueSet = theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrl);
// If we don't have a PID, this came from some source other than the JPA // If we don't have a PID, this came from some source other than the JPA
@ -2029,24 +2137,26 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
Long pid = IDao.RESOURCE_PID.get((IAnyResource) valueSet); Long pid = IDao.RESOURCE_PID.get((IAnyResource) valueSet);
if (pid != null) { if (pid != null) {
if (isValueSetPreExpandedForCodeValidation(valueSet)) { if (isValueSetPreExpandedForCodeValidation(valueSet)) {
IFhirResourceDaoValueSet.ValidateCodeResult outcome = validateCodeIsInPreExpandedValueSet(new ValidationOptions(), valueSet, theCodeSystem, theCode, null, null, null); return validateCodeIsInPreExpandedValueSet(theValidationOptions, valueSet, theCodeSystem, theCode, null, null, null);
if (outcome != null && outcome.isResult()) {
return Optional.of(new VersionIndependentConcept(theCodeSystem, theCode));
}
} }
} }
} }
ValueSet canonicalValueSet = toCanonicalValueSet(valueSet); CodeValidationResult retVal = null;
VersionIndependentConcept wantConcept = new VersionIndependentConcept(theCodeSystem, theCode); if (valueSet != null) {
ValueSetExpansionOptions expansionOptions = new ValueSetExpansionOptions() retVal = new InMemoryTerminologyServerValidationSupport(myContext).validateCodeInValueSet(theValidationSupportContext, theValidationOptions, theCodeSystem, theCode, theDisplay, valueSet);
.setFailOnMissingCodeSystem(false); } else {
String append = " - Unable to locate ValueSet[" + theValueSetUrl + "]";
retVal = createFailureCodeValidationResult(theCodeSystem, theCode, append);
}
if (retVal == null) {
String append = " - Unable to expand ValueSet[" + theValueSetUrl + "]";
retVal = createFailureCodeValidationResult(theCodeSystem, theCode, append);
}
return retVal;
List<VersionIndependentConcept> expansionOutcome = expandValueSetAndReturnVersionIndependentConcepts(expansionOptions, canonicalValueSet, wantConcept);
return expansionOutcome
.stream()
.filter(t -> (theValidationOptions.isInferSystem() || t.getSystem().equals(theCodeSystem)) && t.getCode().equals(theCode))
.findFirst();
} }
@Override @Override
@ -2171,6 +2281,12 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
return false; return false;
} }
@Nullable
protected abstract Coding toCanonicalCoding(@Nullable IBaseDatatype theCoding);
@Nullable
protected abstract CodeableConcept toCanonicalCodeableConcept(@Nullable IBaseDatatype theCodeableConcept);
public static class Job implements HapiJob { public static class Job implements HapiJob {
@Autowired @Autowired
private ITermReadSvc myTerminologySvc; private ITermReadSvc myTerminologySvc;

View File

@ -20,18 +20,23 @@ package ca.uhn.fhir.jpa.term;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
import ca.uhn.fhir.util.VersionIndependentConcept; import ca.uhn.fhir.util.VersionIndependentConcept;
import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.utilities.validation.ValidationOptions; import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -132,8 +137,34 @@ public class TermReadSvcDstu2 extends BaseTermReadSvcImpl {
return retVal; return retVal;
} }
@Nullable
@Override @Override
public IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInPreExpandedValueSet(ValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) { protected Coding toCanonicalCoding(@Nullable IBaseDatatype theCoding) {
Coding retVal = null;
if (theCoding != null) {
CodingDt coding = (CodingDt) theCoding;
retVal = new Coding(coding.getSystem(), coding.getCode(), coding.getDisplay());
}
return retVal;
}
@Nullable
@Override
protected CodeableConcept toCanonicalCodeableConcept(@Nullable IBaseDatatype theCodeableConcept) {
CodeableConcept outcome = null;
if (theCodeableConcept != null) {
outcome = new CodeableConcept();
CodeableConceptDt cc = (CodeableConceptDt) theCodeableConcept;
outcome.setText(cc.getText());
for (CodingDt next : cc.getCoding()) {
outcome.addCoding(toCanonicalCoding(next));
}
}
return outcome;
}
@Override
public CodeValidationResult validateCodeIsInPreExpandedValueSet(ConceptValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View File

@ -12,6 +12,8 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.ValidateUtil; import ca.uhn.fhir.util.ValidateUtil;
import ca.uhn.fhir.util.VersionIndependentConcept; import ca.uhn.fhir.util.VersionIndependentConcept;
import org.hl7.fhir.convertors.VersionConvertor_30_40;
import org.hl7.fhir.convertors.VersionConvertor_40_50;
import org.hl7.fhir.convertors.conv30_40.CodeSystem30_40; import org.hl7.fhir.convertors.conv30_40.CodeSystem30_40;
import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeableConcept; import org.hl7.fhir.dstu3.model.CodeableConcept;
@ -26,6 +28,7 @@ import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nullable;
import java.util.Optional; import java.util.Optional;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -103,6 +106,20 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
return CodeSystem30_40.convertCodeSystem((CodeSystem)theCodeSystem); return CodeSystem30_40.convertCodeSystem((CodeSystem)theCodeSystem);
} }
@Override
@Nullable
protected org.hl7.fhir.r4.model.Coding toCanonicalCoding(IBaseDatatype theCoding) {
return VersionConvertor_30_40.convertCoding((org.hl7.fhir.dstu3.model.Coding) theCoding);
}
@Override
@Nullable
protected org.hl7.fhir.r4.model.CodeableConcept toCanonicalCodeableConcept(IBaseDatatype theCoding) {
return VersionConvertor_30_40.convertCodeableConcept((org.hl7.fhir.dstu3.model.CodeableConcept) theCoding);
}
@Override @Override
public void expandValueSet(ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) { public void expandValueSet(ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) {
ValueSet valueSetToExpand = (ValueSet) theValueSetToExpand; ValueSet valueSetToExpand = (ValueSet) theValueSetToExpand;
@ -130,35 +147,6 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
return valueSetR4; return valueSetR4;
} }
@CoverageIgnore
@Override
public IValidationSupport.CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
Optional<VersionIndependentConcept> codeOpt = Optional.empty();
boolean haveValidated = false;
if (isNotBlank(theValueSetUrl)) {
codeOpt = super.validateCodeInValueSet(theValidationSupportContext, theOptions, theValueSetUrl, theCodeSystem, theCode);
haveValidated = true;
}
if (!haveValidated) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> new VersionIndependentConcept(theCodeSystem, c.getCode())));
}
if (codeOpt != null && codeOpt.isPresent()) {
VersionIndependentConcept code = codeOpt.get();
IValidationSupport.CodeValidationResult retVal = new IValidationSupport.CodeValidationResult()
.setCode(code.getCode());
return retVal;
}
return new IValidationSupport.CodeValidationResult()
.setSeverity(IssueSeverity.ERROR)
.setMessage("Unknown code {" + theCodeSystem + "}" + theCode);
}
@Override @Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) {
return super.lookupCode(theSystem, theCode); return super.lookupCode(theSystem, theCode);
@ -170,7 +158,7 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
} }
@Override @Override
public IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInPreExpandedValueSet(ValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) { public IValidationSupport.CodeValidationResult validateCodeIsInPreExpandedValueSet(ConceptValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) {
ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet, "ValueSet must not be null"); ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet, "ValueSet must not be null");
ValueSet valueSet = (ValueSet) theValueSet; ValueSet valueSet = (ValueSet) theValueSet;
org.hl7.fhir.r4.model.ValueSet valueSetR4 = convertValueSet(valueSet); org.hl7.fhir.r4.model.ValueSet valueSetR4 = convertValueSet(valueSet);

View File

@ -5,28 +5,19 @@ import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4; import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4;
import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException; import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException;
import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.VersionIndependentConcept;
import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;
import javax.transaction.Transactional; import javax.transaction.Transactional;
import java.util.Optional;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/* /*
* #%L * #%L
@ -98,50 +89,29 @@ public class TermReadSvcR4 extends BaseTermReadSvcImpl implements ITermReadSvcR4
return (CodeSystem) theCodeSystem; return (CodeSystem) theCodeSystem;
} }
@CoverageIgnore
@Override
public IValidationSupport.CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
invokeRunnableForUnitTest();
Optional<VersionIndependentConcept> codeOpt = Optional.empty();
boolean haveValidated = false;
if (isNotBlank(theValueSetUrl)) {
codeOpt = super.validateCodeInValueSet(theValidationSupportContext, theOptions, theValueSetUrl, theCodeSystem, theCode);
haveValidated = true;
}
if (!haveValidated) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> new VersionIndependentConcept(theCodeSystem, c.getCode())));
}
if (codeOpt != null && codeOpt.isPresent()) {
VersionIndependentConcept code = codeOpt.get();
IValidationSupport.CodeValidationResult retVal = new IValidationSupport.CodeValidationResult()
.setCode(code.getCode());
return retVal;
}
return new IValidationSupport.CodeValidationResult()
.setSeverity(IssueSeverity.ERROR)
.setMessage("Unknown code {" + theCodeSystem + "}" + theCode);
}
@Override @Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) {
return super.lookupCode(theSystem, theCode); return super.lookupCode(theSystem, theCode);
} }
@Override @Override
public IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInPreExpandedValueSet(ValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) { public IValidationSupport.CodeValidationResult validateCodeIsInPreExpandedValueSet(ConceptValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) {
ValueSet valueSet = (ValueSet) theValueSet; ValueSet valueSet = (ValueSet) theValueSet;
Coding coding = (Coding) theCoding; Coding coding = toCanonicalCoding(theCoding);
CodeableConcept codeableConcept = (CodeableConcept) theCodeableConcept; CodeableConcept codeableConcept = toCanonicalCodeableConcept(theCodeableConcept);
return super.validateCodeIsInPreExpandedValueSet(theOptions, valueSet, theSystem, theCode, theDisplay, coding, codeableConcept); return super.validateCodeIsInPreExpandedValueSet(theOptions, valueSet, theSystem, theCode, theDisplay, coding, codeableConcept);
} }
@Override
protected Coding toCanonicalCoding(IBaseDatatype theCoding) {
return (Coding) theCoding;
}
@Override
protected CodeableConcept toCanonicalCodeableConcept(IBaseDatatype theCodeableConcept) {
return (CodeableConcept) theCodeableConcept;
}
@Override @Override
public boolean isValueSetPreExpandedForCodeValidation(IBaseResource theValueSet) { public boolean isValueSetPreExpandedForCodeValidation(IBaseResource theValueSet) {
ValueSet valueSet = (ValueSet) theValueSet; ValueSet valueSet = (ValueSet) theValueSet;

View File

@ -6,17 +6,15 @@ import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.api.ITermReadSvcR5; import ca.uhn.fhir.jpa.term.api.ITermReadSvcR5;
import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException; import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException;
import ca.uhn.fhir.util.ValidateUtil; import ca.uhn.fhir.util.ValidateUtil;
import ca.uhn.fhir.util.VersionIndependentConcept; import org.hl7.fhir.convertors.VersionConvertor_40_50;
import org.hl7.fhir.convertors.conv40_50.CodeSystem40_50; import org.hl7.fhir.convertors.conv40_50.CodeSystem40_50;
import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r5.model.CodeableConcept; import org.hl7.fhir.r5.model.CodeableConcept;
import org.hl7.fhir.r5.model.Coding; import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.model.ValueSet;
@ -24,13 +22,9 @@ import org.hl7.fhir.utilities.TerminologyServiceOptions;
import org.hl7.fhir.utilities.validation.ValidationOptions; import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nullable;
import javax.transaction.Transactional; import javax.transaction.Transactional;
import java.util.Optional;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/* /*
* #%L * #%L
@ -86,57 +80,18 @@ public class TermReadSvcR5 extends BaseTermReadSvcImpl implements IValidationSup
return org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet(valueSetR5); return org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet(valueSetR5);
} }
@Override
public IValidationSupport.CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
Optional<VersionIndependentConcept> codeOpt = Optional.empty();
boolean haveValidated = false;
if (isNotBlank(theValueSetUrl)) {
codeOpt = super.validateCodeInValueSet(theValidationSupportContext, theOptions, theValueSetUrl, theCodeSystem, theCode);
haveValidated = true;
}
if (!haveValidated) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c -> new VersionIndependentConcept(theCodeSystem, c.getCode())));
}
if (codeOpt != null && codeOpt.isPresent()) {
VersionIndependentConcept code = codeOpt.get();
ConceptDefinitionComponent def = new ConceptDefinitionComponent();
def.setCode(code.getCode());
IValidationSupport.CodeValidationResult retVal = new IValidationSupport.CodeValidationResult()
.setCode(code.getCode());
return retVal;
}
return new IValidationSupport.CodeValidationResult()
.setSeverity(IssueSeverity.ERROR)
.setCode("Unknown code {" + theCodeSystem + "}" + theCode);
}
@Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) {
return super.lookupCode(theSystem, theCode);
}
@Override @Override
public FhirContext getFhirContext() { public FhirContext getFhirContext() {
return myContext; return myContext;
} }
@Override @Override
public IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInPreExpandedValueSet(ValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) { public CodeValidationResult validateCodeIsInPreExpandedValueSet(ConceptValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) {
ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet, "ValueSet must not be null"); ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet, "ValueSet must not be null");
ValueSet valueSet = (ValueSet) theValueSet; ValueSet valueSet = (ValueSet) theValueSet;
org.hl7.fhir.r4.model.ValueSet valueSetR4 = toCanonicalValueSet(valueSet); org.hl7.fhir.r4.model.ValueSet valueSetR4 = toCanonicalValueSet(valueSet);
Coding coding = (Coding) theCoding; org.hl7.fhir.r4.model.Coding codingR4 = toCanonicalCoding(theCoding);
org.hl7.fhir.r4.model.Coding codingR4 = null;
if (coding != null) {
codingR4 = new org.hl7.fhir.r4.model.Coding(coding.getSystem(), coding.getCode(), coding.getDisplay());
}
CodeableConcept codeableConcept = (CodeableConcept) theCodeableConcept; CodeableConcept codeableConcept = (CodeableConcept) theCodeableConcept;
org.hl7.fhir.r4.model.CodeableConcept codeableConceptR4 = null; org.hl7.fhir.r4.model.CodeableConcept codeableConceptR4 = null;
@ -147,9 +102,22 @@ public class TermReadSvcR5 extends BaseTermReadSvcImpl implements IValidationSup
} }
} }
return super.validateCodeIsInPreExpandedValueSet(new TerminologyServiceOptions(), valueSetR4, theSystem, theCode, theDisplay, codingR4, codeableConceptR4); return super.validateCodeIsInPreExpandedValueSet(theOptions, valueSetR4, theSystem, theCode, theDisplay, codingR4, codeableConceptR4);
} }
@Override
@Nullable
protected org.hl7.fhir.r4.model.Coding toCanonicalCoding(IBaseDatatype theCoding) {
return VersionConvertor_40_50.convertCoding((Coding) theCoding);
}
@Override
@Nullable
protected org.hl7.fhir.r4.model.CodeableConcept toCanonicalCodeableConcept(IBaseDatatype theCoding) {
return VersionConvertor_40_50.convertCodeableConcept((CodeableConcept) theCoding);
}
@Override @Override
protected org.hl7.fhir.r4.model.ValueSet toCanonicalValueSet(IBaseResource theValueSet) throws org.hl7.fhir.exceptions.FHIRException { protected org.hl7.fhir.r4.model.ValueSet toCanonicalValueSet(IBaseResource theValueSet) throws org.hl7.fhir.exceptions.FHIRException {
return org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet((ValueSet) theValueSet); return org.hl7.fhir.convertors.conv40_50.ValueSet40_50.convertValueSet((ValueSet) theValueSet);

View File

@ -1,19 +1,22 @@
package ca.uhn.fhir.jpa.term.api; package ca.uhn.fhir.jpa.term.api;
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.api.model.TranslationRequest; import ca.uhn.fhir.jpa.api.model.TranslationRequest;
import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement; import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement;
import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget; import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.IValueSetConceptAccumulator; import ca.uhn.fhir.jpa.term.IValueSetConceptAccumulator;
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
import ca.uhn.fhir.util.VersionIndependentConcept; import ca.uhn.fhir.util.VersionIndependentConcept;
import org.hl7.fhir.instance.model.api.IBaseCoding; import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseResource; 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.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.ConceptMap;
@ -105,7 +108,12 @@ public interface ITermReadSvc extends IValidationSupport {
/** /**
* Version independent * Version independent
*/ */
IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInPreExpandedValueSet(ValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept); CodeValidationResult validateCode(ConceptValidationOptions theOptions, IIdType theValueSetId, String theValueSetUrl, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept);
/**
* Version independent
*/
CodeValidationResult validateCodeIsInPreExpandedValueSet(ConceptValidationOptions theOptions, IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept);
boolean isValueSetPreExpandedForCodeValidation(ValueSet theValueSet); boolean isValueSetPreExpandedForCodeValidation(ValueSet theValueSet);
@ -114,5 +122,4 @@ public interface ITermReadSvc extends IValidationSupport {
*/ */
boolean isValueSetPreExpandedForCodeValidation(IBaseResource theValueSet); boolean isValueSetPreExpandedForCodeValidation(IBaseResource theValueSet);
} }

View File

@ -119,7 +119,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
retVal.setDriver(new org.h2.Driver()); retVal.setDriver(new org.h2.Driver());
retVal.setUrl("jdbc:h2:mem:testdb_r4"); retVal.setUrl("jdbc:h2:mem:testdb_r4");
retVal.setMaxWaitMillis(10000); retVal.setMaxWaitMillis(30000);
retVal.setUsername(""); retVal.setUsername("");
retVal.setPassword(""); retVal.setPassword("");
retVal.setMaxTotal(ourMaxThreads); retVal.setMaxTotal(ourMaxThreads);

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.jpa.dao.dstu2; package ca.uhn.fhir.jpa.dao.dstu2;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt;
import ca.uhn.fhir.model.dstu2.resource.ValueSet; import ca.uhn.fhir.model.dstu2.resource.ValueSet;
@ -8,9 +8,7 @@ import ca.uhn.fhir.model.primitive.CodeDt;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -40,30 +38,42 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test {
} }
@Test @Test
public void testValidateCodeOperationByCodeAndSystemBad() { public void testValidateCodeOperationByCodeAndSystemBadCode() {
UriDt valueSetIdentifier = null; UriDt valueSetIdentifier = new UriDt("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
IdDt id = null; IdDt id = null;
CodeDt code = new CodeDt("8450-9-XXX"); CodeDt code = new CodeDt("8450-9-XXX");
UriDt system = new UriDt("http://loinc.org"); UriDt system = new UriDt("http://loinc.org");
StringDt display = null; StringDt display = null;
CodingDt coding = null; CodingDt coding = null;
CodeableConceptDt codeableConcept = null; CodeableConceptDt codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertFalse(result.isResult()); assertFalse(result.isOk());
} }
@Test @Test
public void testValidateCodeOperationByCodeAndSystemGood() { public void testValidateCodeOperationByCodeAndSystemBadSystem() {
UriDt valueSetIdentifier = null; UriDt valueSetIdentifier = new UriDt("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
IdDt id = null;
CodeDt code = new CodeDt("8450-9-XXX");
UriDt system = new UriDt("http://zzz");
StringDt display = null;
CodingDt coding = null;
CodeableConceptDt codeableConcept = null;
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertFalse(result.isOk());
}
@Test
public void testValidateCodeOperationByIdentifierCodeInCsButNotInVs() {
UriDt valueSetIdentifier = new UriDt("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
IdDt id = null; IdDt id = null;
CodeDt code = new CodeDt("8450-9"); CodeDt code = new CodeDt("8450-9");
UriDt system = new UriDt("http://loinc.org"); UriDt system = new UriDt("http://loinc.org");
StringDt display = null; StringDt display = null;
CodingDt coding = null; CodingDt coding = null;
CodeableConceptDt codeableConcept = null; CodeableConceptDt codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertFalse(result.isOk());
assertEquals("Systolic blood pressure--expiration", result.getDisplay());
} }
@Test @Test
@ -75,8 +85,8 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test {
StringDt display = null; StringDt display = null;
CodingDt coding = null; CodingDt coding = null;
CodeableConceptDt codeableConcept = null; CodeableConceptDt codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -89,9 +99,10 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test {
StringDt display = new StringDt("Systolic blood pressure at First encounterXXXX"); StringDt display = new StringDt("Systolic blood pressure at First encounterXXXX");
CodingDt coding = null; CodingDt coding = null;
CodeableConceptDt codeableConcept = null; CodeableConceptDt codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertFalse(result.isResult()); assertFalse(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
assertEquals("Concept Display \"Systolic blood pressure at First encounterXXXX\" does not match expected \"Systolic blood pressure at First encounter\"", result.getMessage());
} }
@Test @Test
@ -103,8 +114,8 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test {
StringDt display = new StringDt("Systolic blood pressure at First encounter"); StringDt display = new StringDt("Systolic blood pressure at First encounter");
CodingDt coding = null; CodingDt coding = null;
CodeableConceptDt codeableConcept = null; CodeableConceptDt codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -117,8 +128,8 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test {
StringDt display = null; StringDt display = null;
CodingDt coding = null; CodingDt coding = null;
CodeableConceptDt codeableConcept = new CodeableConceptDt("http://loinc.org", "11378-7"); CodeableConceptDt codeableConcept = new CodeableConceptDt("http://loinc.org", "11378-7");
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -131,8 +142,8 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test {
StringDt display = null; StringDt display = null;
CodingDt coding = null; CodingDt coding = null;
CodeableConceptDt codeableConcept = null; CodeableConceptDt codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }

View File

@ -1,7 +1,6 @@
package ca.uhn.fhir.jpa.dao.dstu3; package ca.uhn.fhir.jpa.dao.dstu3;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.entity.TermValueSet; import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum; import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
@ -62,7 +61,7 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test {
assertEquals(TermValueSetPreExpansionStatusEnum.NOT_EXPANDED, vsEntity.getExpansionStatus()); assertEquals(TermValueSetPreExpansionStatusEnum.NOT_EXPANDED, vsEntity.getExpansionStatus());
}); });
IFhirResourceDaoValueSet.ValidateCodeResult validationOutcome; IValidationSupport.CodeValidationResult validationOutcome;
UriType vsIdentifier = new UriType("http://decor.nictiz.nl/fhir/ValueSet/2.16.840.1.113883.2.4.3.11.60.40.2.20.5.2--20171231000000"); UriType vsIdentifier = new UriType("http://decor.nictiz.nl/fhir/ValueSet/2.16.840.1.113883.2.4.3.11.60.40.2.20.5.2--20171231000000");
CodeType code = new CodeType(); CodeType code = new CodeType();
CodeType system = new CodeType("urn:iso:std:iso:3166"); CodeType system = new CodeType("urn:iso:std:iso:3166");
@ -70,12 +69,12 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test {
// Validate good // Validate good
code.setValue("NL"); code.setValue("NL");
validationOutcome = myValueSetDao.validateCode(vsIdentifier, null, code, system, null, null, null, mySrd); validationOutcome = myValueSetDao.validateCode(vsIdentifier, null, code, system, null, null, null, mySrd);
assertEquals(true, validationOutcome.isResult()); assertEquals(true, validationOutcome.isOk());
// Validate bad // Validate bad
code.setValue("QQ"); code.setValue("QQ");
validationOutcome = myValueSetDao.validateCode(vsIdentifier, null, code, system, null, null, null, mySrd); validationOutcome = myValueSetDao.validateCode(vsIdentifier, null, code, system, null, null, null, mySrd);
assertEquals(false, validationOutcome.isResult()); assertEquals(false, validationOutcome.isOk());
myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
@ -87,12 +86,12 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test {
// Validate good // Validate good
code.setValue("NL"); code.setValue("NL");
validationOutcome = myValueSetDao.validateCode(vsIdentifier, null, code, system, null, null, null, mySrd); validationOutcome = myValueSetDao.validateCode(vsIdentifier, null, code, system, null, null, null, mySrd);
assertEquals(true, validationOutcome.isResult()); assertEquals(true, validationOutcome.isOk());
// Validate bad // Validate bad
code.setValue("QQ"); code.setValue("QQ");
validationOutcome = myValueSetDao.validateCode(vsIdentifier, null, code, system, null, null, null, mySrd); validationOutcome = myValueSetDao.validateCode(vsIdentifier, null, code, system, null, null, null, mySrd);
assertEquals(false, validationOutcome.isResult()); assertEquals(false, validationOutcome.isOk());
} }
@ -189,33 +188,6 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test {
assertThat(resp, not(containsString("<code value=\"8450-9\"/>"))); assertThat(resp, not(containsString("<code value=\"8450-9\"/>")));
} }
@Test
public void testValidateCodeOperationByCodeAndSystemBad() {
UriType valueSetIdentifier = null;
IdType id = null;
CodeType code = new CodeType("8450-9-XXX");
UriType system = new UriType("http://acme.org");
StringType display = null;
Coding coding = null;
CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertFalse(result.isResult());
}
@Test
public void testValidateCodeOperationByCodeAndSystemGood() {
UriType valueSetIdentifier = null;
IdType id = null;
CodeType code = new CodeType("8450-9");
UriType system = new UriType("http://acme.org");
StringType display = null;
Coding coding = null;
CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult());
assertEquals("Systolic blood pressure--expiration", result.getDisplay());
}
@Test @Test
public void testValidateCodeOperationByIdentifierAndCodeAndSystem() { public void testValidateCodeOperationByIdentifierAndCodeAndSystem() {
UriType valueSetIdentifier = new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"); UriType valueSetIdentifier = new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
@ -225,8 +197,8 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test {
StringType display = null; StringType display = null;
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = null; CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -239,8 +211,8 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test {
StringType display = new StringType("Systolic blood pressure at First encounterXXXX"); StringType display = new StringType("Systolic blood pressure at First encounterXXXX");
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = null; CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertFalse(result.isResult()); assertFalse(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -253,8 +225,8 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test {
StringType display = new StringType("Systolic blood pressure at First encounter"); StringType display = new StringType("Systolic blood pressure at First encounter");
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = null; CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -267,8 +239,8 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test {
StringType display = null; StringType display = null;
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = null; CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -282,8 +254,8 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test {
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = new CodeableConcept(); CodeableConcept codeableConcept = new CodeableConcept();
codeableConcept.addCoding().setSystem("http://acme.org").setCode("11378-7"); codeableConcept.addCoding().setSystem("http://acme.org").setCode("11378-7");
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -295,10 +267,10 @@ public class FhirResourceDaoDstu3ValueSetTest extends BaseJpaDstu3Test {
StringType vsIdentifier = new StringType("http://hl7.org/fhir/ValueSet/administrative-gender"); StringType vsIdentifier = new StringType("http://hl7.org/fhir/ValueSet/administrative-gender");
StringType code = new StringType("male"); StringType code = new StringType("male");
StringType system = new StringType("http://hl7.org/fhir/administrative-gender"); StringType system = new StringType("http://hl7.org/fhir/administrative-gender");
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(vsIdentifier, null, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(vsIdentifier, null, code, system, display, coding, codeableConcept, mySrd);
ourLog.info(result.getMessage()); ourLog.info(result.getMessage());
assertTrue(result.isResult(), result.getMessage()); assertTrue(result.isOk(), result.getMessage());
} }

View File

@ -75,7 +75,7 @@ public class FhirResourceDaoR4ConcurrentWriteTest extends BaseJpaR4Test {
when(mySrd.getHeaders(eq(UserRequestRetryVersionConflictsInterceptor.HEADER_NAME))).thenReturn(Collections.singletonList(value)); when(mySrd.getHeaders(eq(UserRequestRetryVersionConflictsInterceptor.HEADER_NAME))).thenReturn(Collections.singletonList(value));
List<Future<?>> futures = new ArrayList<>(); List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) { for (int i = 0; i < 5; i++) {
Patient p = new Patient(); Patient p = new Patient();
p.setId("ABC"); p.setId("ABC");
p.setActive(true); p.setActive(true);
@ -130,7 +130,7 @@ public class FhirResourceDaoR4ConcurrentWriteTest extends BaseJpaR4Test {
mySearchParamRegistry.forceRefresh(); mySearchParamRegistry.forceRefresh();
List<Future<?>> futures = new ArrayList<>(); List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) { for (int i = 0; i < 5; i++) {
Patient p = new Patient(); Patient p = new Patient();
p.setGender(Enumerations.AdministrativeGender.MALE); p.setGender(Enumerations.AdministrativeGender.MALE);
p.addIdentifier().setValue("VAL" + i); p.addIdentifier().setValue("VAL" + i);
@ -185,7 +185,7 @@ public class FhirResourceDaoR4ConcurrentWriteTest extends BaseJpaR4Test {
}); });
List<Future<?>> futures = new ArrayList<>(); List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) { for (int i = 0; i < 5; i++) {
// Submit an update // Submit an update
Patient p = new Patient(); Patient p = new Patient();
p.setId(patientId); p.setId(patientId);
@ -224,7 +224,7 @@ public class FhirResourceDaoR4ConcurrentWriteTest extends BaseJpaR4Test {
when(mySrd.getHeaders(eq(UserRequestRetryVersionConflictsInterceptor.HEADER_NAME))).thenReturn(Collections.emptyList()); when(mySrd.getHeaders(eq(UserRequestRetryVersionConflictsInterceptor.HEADER_NAME))).thenReturn(Collections.emptyList());
List<Future<?>> futures = new ArrayList<>(); List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) { for (int i = 0; i < 5; i++) {
Patient p = new Patient(); Patient p = new Patient();
p.setId("ABC"); p.setId("ABC");
p.setActive(true); p.setActive(true);
@ -260,7 +260,7 @@ public class FhirResourceDaoR4ConcurrentWriteTest extends BaseJpaR4Test {
@Test @Test
public void testNoRetryInterceptor() { public void testNoRetryInterceptor() {
List<Future<?>> futures = new ArrayList<>(); List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) { for (int i = 0; i < 5; i++) {
Patient p = new Patient(); Patient p = new Patient();
p.setId("ABC"); p.setId("ABC");
p.setActive(true); p.setActive(true);
@ -300,7 +300,7 @@ public class FhirResourceDaoR4ConcurrentWriteTest extends BaseJpaR4Test {
when(mySrd.getHeaders(eq(UserRequestRetryVersionConflictsInterceptor.HEADER_NAME))).thenReturn(Collections.emptyList()); when(mySrd.getHeaders(eq(UserRequestRetryVersionConflictsInterceptor.HEADER_NAME))).thenReturn(Collections.emptyList());
List<Future<?>> futures = new ArrayList<>(); List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) { for (int i = 0; i < 5; i++) {
Patient p = new Patient(); Patient p = new Patient();
p.setId("ABC"); p.setId("ABC");
p.setActive(true); p.setActive(true);
@ -345,7 +345,7 @@ public class FhirResourceDaoR4ConcurrentWriteTest extends BaseJpaR4Test {
IIdType pId = myPatientDao.create(p).getId().toUnqualifiedVersionless(); IIdType pId = myPatientDao.create(p).getId().toUnqualifiedVersionless();
List<Future<?>> futures = new ArrayList<>(); List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) { for (int i = 0; i < 5; i++) {
Parameters patch = new Parameters(); Parameters patch = new Parameters();
Parameters.ParametersParameterComponent operation = patch.addParameter(); Parameters.ParametersParameterComponent operation = patch.addParameter();
@ -382,7 +382,7 @@ public class FhirResourceDaoR4ConcurrentWriteTest extends BaseJpaR4Test {
// Make sure we saved the object // Make sure we saved the object
Patient patient = myPatientDao.read(pId); Patient patient = myPatientDao.read(pId);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient)); ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient));
assertEquals("11", patient.getMeta().getVersionId()); assertEquals("6", patient.getMeta().getVersionId());
} }
@ -400,7 +400,7 @@ public class FhirResourceDaoR4ConcurrentWriteTest extends BaseJpaR4Test {
when(srd.getInterceptorBroadcaster()).thenReturn(new InterceptorService()); when(srd.getInterceptorBroadcaster()).thenReturn(new InterceptorService());
List<Future<?>> futures = new ArrayList<>(); List<Future<?>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) { for (int i = 0; i < 5; i++) {
Patient p = new Patient(); Patient p = new Patient();
p.setId("ABC"); p.setId("ABC");

View File

@ -9,7 +9,10 @@ import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum; import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl; import ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl;
import ca.uhn.fhir.jpa.term.TerminologyLoaderSvcLoincTest;
import ca.uhn.fhir.jpa.term.ZipCollectionBuilder;
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet; import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain;
@ -36,9 +39,11 @@ import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Condition; import org.hl7.fhir.r4.model.Condition;
import org.hl7.fhir.r4.model.DateTimeType; import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.ElementDefinition;
import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.Group; import org.hl7.fhir.r4.model.Group;
import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.IdType;
@ -49,11 +54,13 @@ import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Organization; import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Practitioner; import org.hl7.fhir.r4.model.Practitioner;
import org.hl7.fhir.r4.model.Quantity;
import org.hl7.fhir.r4.model.Questionnaire; import org.hl7.fhir.r4.model.Questionnaire;
import org.hl7.fhir.r4.model.QuestionnaireResponse; import org.hl7.fhir.r4.model.QuestionnaireResponse;
import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.StructureDefinition; import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.UriType;
import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r5.utils.IResourceValidator; import org.hl7.fhir.r5.utils.IResourceValidator;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -73,6 +80,7 @@ import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@ -95,6 +103,105 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
@Autowired @Autowired
private ValidationSettings myValidationSettings; private ValidationSettings myValidationSettings;
@Test
public void testValidateCodeInValueSetWithUnknownCodeSystem() {
myValidationSupport.fetchCodeSystem("http://not-exist"); // preload DefaultProfileValidationSupport
ValueSet vs = new ValueSet();
vs.setUrl("http://vs");
vs
.getCompose()
.addInclude()
.setSystem("http://cs")
.addConcept(new ValueSet.ConceptReferenceComponent(new CodeType("code1")))
.addConcept(new ValueSet.ConceptReferenceComponent(new CodeType("code2")));
myValueSetDao.create(vs);
StructureDefinition sd = new StructureDefinition();
sd.setDerivation(StructureDefinition.TypeDerivationRule.CONSTRAINT);
sd.setType("Observation");
sd.setUrl("http://sd");
sd.setBaseDefinition("http://hl7.org/fhir/StructureDefinition/Observation");
sd.getDifferential()
.addElement()
.setPath("Observation.value[x]")
.addType(new ElementDefinition.TypeRefComponent(new UriType("Quantity")))
.setBinding(new ElementDefinition.ElementDefinitionBindingComponent().setStrength(Enumerations.BindingStrength.REQUIRED).setValueSet("http://vs"))
.setId("Observation.value[x]");
myStructureDefinitionDao.create(sd);
Observation obs = new Observation();
obs.getMeta().addProfile("http://sd");
obs.getText().setDivAsString("<div>Hello</div>");
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
obs.getCode().setText("hello");
obs.setSubject(new Reference("Patient/123"));
obs.addPerformer(new Reference("Practitioner/123"));
obs.setEffective(DateTimeType.now());
obs.setStatus(ObservationStatus.FINAL);
OperationOutcome oo;
// Valid code
obs.setValue(new Quantity().setSystem("http://cs").setCode("code1").setValue(123));
oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals("No issues detected during validation", oo.getIssueFirstRep().getDiagnostics(), encode(oo));
// Invalid code
obs.setValue(new Quantity().setSystem("http://cs").setCode("code99").setValue(123));
oo = validateAndReturnOutcome(obs);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals("Could not confirm that the codes provided are in the value set http://vs, and a code from this value set is required", oo.getIssueFirstRep().getDiagnostics(), encode(oo));
}
@Test
public void testGenerateSnapshotOnStructureDefinitionWithNoBase() {
// No base populated here, which isn't valid
StructureDefinition sd = new StructureDefinition();
sd.setDerivation(StructureDefinition.TypeDerivationRule.CONSTRAINT);
sd.setUrl("http://sd");
sd.getDifferential()
.addElement()
.setPath("Observation.value[x]")
.addType(new ElementDefinition.TypeRefComponent(new UriType("string")))
.setId("Observation.value[x]");
try {
myStructureDefinitionDao.generateSnapshot(sd, null, null, null);
fail();
} catch (PreconditionFailedException e) {
assertEquals("StructureDefinition[id=null, url=http://sd] has no base", e.getMessage());
}
myStructureDefinitionDao.create(sd);
Observation obs = new Observation();
obs.getMeta().addProfile("http://sd");
obs.getText().setDivAsString("<div>Hello</div>");
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
obs.getCode().setText("hello");
obs.setSubject(new Reference("Patient/123"));
obs.addPerformer(new Reference("Practitioner/123"));
obs.setEffective(DateTimeType.now());
obs.setStatus(ObservationStatus.FINAL);
OperationOutcome oo;
// Valid code
obs.setValue(new Quantity().setSystem("http://cs").setCode("code1").setValue(123));
try {
myObservationDao.validate(obs, null, null, null, ValidationModeEnum.CREATE, null, mySrd);
fail();
} catch (PreconditionFailedException e) {
assertEquals("StructureDefinition[id=null, url=http://sd] has no base", e.getMessage());
}
}
/** /**
* Use a valueset that explicitly brings in some UCUM codes * Use a valueset that explicitly brings in some UCUM codes
*/ */
@ -591,16 +698,10 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
obs.getCode().getCodingFirstRep().setSystem("http://example.com/codesystem"); obs.getCode().getCodingFirstRep().setSystem("http://example.com/codesystem");
obs.getCode().getCodingFirstRep().setCode("foo-foo"); obs.getCode().getCodingFirstRep().setCode("foo-foo");
obs.getCode().getCodingFirstRep().setDisplay("Some Code"); obs.getCode().getCodingFirstRep().setDisplay("Some Code");
try { outcome = (OperationOutcome) myObservationDao.validate(obs, null, null, null, ValidationModeEnum.CREATE, "http://example.com/structuredefinition", mySrd).getOperationOutcome();
outcome = (OperationOutcome) myObservationDao.validate(obs, null, null, null, ValidationModeEnum.CREATE, "http://example.com/structuredefinition", mySrd).getOperationOutcome(); ourLog.info("Outcome: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
ourLog.info("Outcome: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); assertEquals("Unknown code in fragment CodeSystem 'http://example.com/codesystem#foo-foo'", outcome.getIssueFirstRep().getDiagnostics());
fail(); assertEquals(OperationOutcome.IssueSeverity.WARNING, outcome.getIssueFirstRep().getSeverity());
} catch (PreconditionFailedException e) {
OperationOutcome oo = (OperationOutcome) e.getOperationOutcome();
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertEquals("None of the codes provided are in the value set http://example.com/valueset (http://example.com/valueset), and a code from this value set is required) (codes = http://example.com/codesystem#foo-foo)", oo.getIssueFirstRep().getDiagnostics());
assertEquals(OperationOutcome.IssueSeverity.ERROR, oo.getIssueFirstRep().getSeverity());
}
// Correct codesystem, Code in codesystem // Correct codesystem, Code in codesystem
obs.getCode().getCodingFirstRep().setSystem("http://example.com/codesystem"); obs.getCode().getCodingFirstRep().setSystem("http://example.com/codesystem");
@ -1500,4 +1601,20 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
} }
@Test
public void testKnownCodeSystemUnknownValueSetUri() {
CodeSystem cs = new CodeSystem();
cs.setUrl(ITermLoaderSvc.LOINC_URI);
cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
cs.addConcept().setCode("10013-1");
myCodeSystemDao.create(cs);
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(new UriType("http://fooVs"), null, new StringType("10013-1"), new StringType(ITermLoaderSvc.LOINC_URI), null, null, null, mySrd);
assertFalse(result.isOk());
assertEquals("Unknown code {http://loinc.org}10013-1 - Unable to locate ValueSet[http://fooVs]", result.getMessage());
}
} }

View File

@ -1,10 +1,10 @@
package ca.uhn.fhir.jpa.dao.r4; package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet; import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem;
@ -15,7 +15,6 @@ import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType; import org.hl7.fhir.r4.model.UriType;
import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -49,14 +48,14 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
public void before02() throws IOException { public void before02() throws IOException {
ValueSet upload = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); ValueSet upload = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml");
myExtensionalVsId = myValueSetDao.create(upload, mySrd).getId().toUnqualifiedVersionless(); myExtensionalVsId = myValueSetDao.create(upload, mySrd).getId().toUnqualifiedVersionless();
CodeSystem upload2 = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs.xml"); CodeSystem upload2 = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs.xml");
myCodeSystemDao.create(upload2, mySrd).getId().toUnqualifiedVersionless(); myCodeSystemDao.create(upload2, mySrd).getId().toUnqualifiedVersionless();
} }
@Test @Test
public void testValidateCodeOperationByCodeAndSystemBad() { public void testValidateCodeOperationNoValueSet() {
UriType valueSetIdentifier = null; UriType valueSetIdentifier = null;
IdType id = null; IdType id = null;
CodeType code = new CodeType("8450-9-XXX"); CodeType code = new CodeType("8450-9-XXX");
@ -64,22 +63,12 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
StringType display = null; StringType display = null;
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = null; CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); try {
assertFalse(result.isResult()); myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
} fail();
} catch (InvalidRequestException e) {
@Test assertEquals("Either ValueSet ID or ValueSet identifier or system and code must be provided. Unable to validate.", e.getMessage());
public void testValidateCodeOperationByCodeAndSystemGood() { }
UriType valueSetIdentifier = null;
IdType id = null;
CodeType code = new CodeType("8450-9");
UriType system = new UriType("http://acme.org");
StringType display = null;
Coding coding = null;
CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult());
assertEquals("Systolic blood pressure--expiration", result.getDisplay());
} }
@Test @Test
@ -91,8 +80,8 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
StringType display = null; StringType display = null;
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = null; CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -105,9 +94,10 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
StringType display = new StringType("Systolic blood pressure at First encounterXXXX"); StringType display = new StringType("Systolic blood pressure at First encounterXXXX");
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = null; CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertFalse(result.isResult()); assertFalse(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
assertEquals("Concept Display \"Systolic blood pressure at First encounterXXXX\" does not match expected \"Systolic blood pressure at First encounter\"", result.getMessage());
} }
@Test @Test
@ -119,8 +109,8 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
StringType display = new StringType("Systolic blood pressure at First encounter"); StringType display = new StringType("Systolic blood pressure at First encounter");
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = null; CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -134,8 +124,8 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = new CodeableConcept(); CodeableConcept codeableConcept = new CodeableConcept();
codeableConcept.addCoding().setSystem("http://acme.org").setCode("11378-7"); codeableConcept.addCoding().setSystem("http://acme.org").setCode("11378-7");
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -151,18 +141,18 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = new CodeableConcept(); CodeableConcept codeableConcept = new CodeableConcept();
codeableConcept.addCoding().setSystem("http://acme.org").setCode("11378-7"); codeableConcept.addCoding().setSystem("http://acme.org").setCode("11378-7");
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
myTerminologyDeferredStorageSvc.saveDeferred(); myTerminologyDeferredStorageSvc.saveDeferred();
result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -175,8 +165,8 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
StringType display = null; StringType display = null;
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = null; CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -191,18 +181,18 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
StringType display = null; StringType display = null;
Coding coding = null; Coding coding = null;
CodeableConcept codeableConcept = null; CodeableConcept codeableConcept = null;
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
myTerminologyDeferredStorageSvc.saveDeferred(); myTerminologyDeferredStorageSvc.saveDeferred();
result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -213,17 +203,17 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
ValueSet expanded = myValueSetDao.expand(myExtensionalVsId, null, mySrd); ValueSet expanded = myValueSetDao.expand(myExtensionalVsId, null, mySrd);
resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp); ourLog.info(resp);
assertThat(resp, containsString("<ValueSet xmlns=\"http://hl7.org/fhir\">")); assertThat(resp, containsString("<ValueSet xmlns=\"http://hl7.org/fhir\">"));
assertThat(resp, containsString("<expansion>")); assertThat(resp, containsString("<expansion>"));
assertThat(resp, containsString("<contains>")); assertThat(resp, containsString("<contains>"));
assertThat(resp, containsString("<system value=\"http://acme.org\"/>")); assertThat(resp, containsString("<system value=\"http://acme.org\"/>"));
assertThat(resp, containsString("<code value=\"8450-9\"/>")); assertThat(resp, containsString("<code value=\"8450-9\"/>"));
assertThat(resp, containsString("<display value=\"Systolic blood pressure--expiration\"/>")); assertThat(resp, containsString("<display value=\"Systolic blood pressure--expiration\"/>"));
assertThat(resp, containsString("</contains>")); assertThat(resp, containsString("</contains>"));
assertThat(resp, containsString("<contains>")); assertThat(resp, containsString("<contains>"));
assertThat(resp, containsString("<system value=\"http://acme.org\"/>")); assertThat(resp, containsString("<system value=\"http://acme.org\"/>"));
assertThat(resp, containsString("<code value=\"11378-7\"/>")); assertThat(resp, containsString("<code value=\"11378-7\"/>"));
assertThat(resp, containsString("<display value=\"Systolic blood pressure at First encounter\"/>")); assertThat(resp, containsString("<display value=\"Systolic blood pressure at First encounter\"/>"));
assertThat(resp, containsString("</contains>")); assertThat(resp, containsString("</contains>"));
assertThat(resp, containsString("</expansion>")); assertThat(resp, containsString("</expansion>"));
@ -236,12 +226,12 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
ourLog.info(resp); ourLog.info(resp);
//@formatter:off //@formatter:off
assertThat(resp, stringContainsInOrder( assertThat(resp, stringContainsInOrder(
"<code value=\"11378-7\"/>", "<code value=\"11378-7\"/>",
"<display value=\"Systolic blood pressure at First encounter\"/>")); "<display value=\"Systolic blood pressure at First encounter\"/>"));
//@formatter:on //@formatter:on
} }
@Test @Test
public void testExpandByValueSet_ExceedsMaxSize() { public void testExpandByValueSet_ExceedsMaxSize() {
// Add a bunch of codes // Add a bunch of codes
@ -265,7 +255,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
} }
} }
@Test @Test
public void testValidateCodeAgainstBuiltInValueSetAndCodeSystemWithValidCode() { public void testValidateCodeAgainstBuiltInValueSetAndCodeSystemWithValidCode() {
IPrimitiveType<String> display = null; IPrimitiveType<String> display = null;
@ -274,14 +264,14 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
StringType vsIdentifier = new StringType("http://hl7.org/fhir/ValueSet/administrative-gender"); StringType vsIdentifier = new StringType("http://hl7.org/fhir/ValueSet/administrative-gender");
StringType code = new StringType("male"); StringType code = new StringType("male");
StringType system = new StringType("http://hl7.org/fhir/administrative-gender"); StringType system = new StringType("http://hl7.org/fhir/administrative-gender");
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(vsIdentifier, null, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(vsIdentifier, null, code, system, display, coding, codeableConcept, mySrd);
ourLog.info(result.getMessage()); ourLog.info(result.getMessage());
assertTrue( result.isResult(), result.getMessage()); assertTrue(result.isOk(), result.getMessage());
assertEquals("Male", result.getDisplay()); assertEquals("Male", result.getDisplay());
} }
} }

View File

@ -45,23 +45,7 @@ public class ResourceProviderDstu2ValueSetTest extends BaseResourceProviderDstu2
.operation() .operation()
.onInstance(myExtensionalVsId) .onInstance(myExtensionalVsId)
.named("validate-code") .named("validate-code")
.withParameter(Parameters.class, "code", new CodeDt("8495-4")) .withParameter(Parameters.class, "code", new CodeDt("11378-7"))
.andParameter("system", new UriDt("http://loinc.org"))
.execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(new BooleanDt(true), respParam.getParameter().get(0).getValue());
}
@Test
public void testValidateCodeOperationByCodeAndSystemType() {
Parameters respParam = ourClient
.operation()
.onType(ValueSet.class)
.named("validate-code")
.withParameter(Parameters.class, "code", new CodeDt("8450-9"))
.andParameter("system", new UriDt("http://loinc.org")) .andParameter("system", new UriDt("http://loinc.org"))
.execute(); .execute();

View File

@ -749,6 +749,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
.named("validate-code") .named("validate-code")
.withParameter(Parameters.class, "code", new CodeType("8450-9")) .withParameter(Parameters.class, "code", new CodeType("8450-9"))
.andParameter("system", new UriType("http://acme.org")) .andParameter("system", new UriType("http://acme.org"))
.andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"))
.execute(); .execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
@ -780,11 +781,8 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
assertEquals("result", respParam.getParameter().get(0).getName()); assertEquals("result", respParam.getParameter().get(0).getName());
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("message", respParam.getParameter().get(1).getName()); assertEquals("display", respParam.getParameter().get(1).getName());
assertThat(((StringType) respParam.getParameter().get(1).getValue()).getValue(), Matchers.containsStringIgnoringCase("succeeded")); assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals("Male", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
} }
/** /**
@ -810,11 +808,8 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
assertEquals("result", respParam.getParameter().get(0).getName()); assertEquals("result", respParam.getParameter().get(0).getName());
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("message", respParam.getParameter().get(1).getName()); assertEquals("display", respParam.getParameter().get(1).getName());
assertThat(((StringType) respParam.getParameter().get(1).getValue()).getValue(), Matchers.containsStringIgnoringCase("succeeded")); assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals("Male", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
} }
@AfterEach @AfterEach

View File

@ -10,9 +10,9 @@ import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.entity.TermValueSetConcept; import ca.uhn.fhir.jpa.entity.TermValueSetConcept;
import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation; import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation;
import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum; import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
@ -54,6 +54,7 @@ import java.util.Optional;
import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM; import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM;
import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_VALUE_SET; import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_VALUE_SET;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.containsStringIgnoringCase; import static org.hamcrest.Matchers.containsStringIgnoringCase;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
@ -61,7 +62,6 @@ import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertSame;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
@ -98,11 +98,11 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
private void persistCodeSystem(CodeSystem theCodeSystem) { private void persistCodeSystem(CodeSystem theCodeSystem) {
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() { new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
@Override @Override
protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) { protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) {
myExtensionalCsId = myCodeSystemDao.create(theCodeSystem, mySrd).getId().toUnqualifiedVersionless(); myExtensionalCsId = myCodeSystemDao.create(theCodeSystem, mySrd).getId().toUnqualifiedVersionless();
} }
}); });
myCodeSystemDao.readEntity(myExtensionalCsId, null).getId(); myCodeSystemDao.readEntity(myExtensionalCsId, null).getId();
} }
@ -156,7 +156,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
createLocalVsWithUnknownCode(codeSystem); createLocalVsWithUnknownCode(codeSystem);
} }
private void createLocalCsAndVs() { private void createLocalCs() {
CodeSystem codeSystem = new CodeSystem(); CodeSystem codeSystem = new CodeSystem();
codeSystem.setUrl(URL_MY_CODE_SYSTEM); codeSystem.setUrl(URL_MY_CODE_SYSTEM);
codeSystem.setContent(CodeSystemContentMode.COMPLETE); codeSystem.setContent(CodeSystemContentMode.COMPLETE);
@ -671,8 +671,8 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
.named("$expand") .named("$expand")
.withNoParameters(Parameters.class) .withNoParameters(Parameters.class)
.returnResourceType(ValueSet.class) .returnResourceType(ValueSet.class)
.execute(); .execute();
ourLog.info("Expanded: {}",myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expanded)); ourLog.info("Expanded: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expanded));
assertEquals(1, expanded.getExpansion().getContains().size()); assertEquals(1, expanded.getExpansion().getContains().size());
// Update the CodeSystem URL and Codes // Update the CodeSystem URL and Codes
@ -696,12 +696,11 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
.withNoParameters(Parameters.class) .withNoParameters(Parameters.class)
.returnResourceType(ValueSet.class) .returnResourceType(ValueSet.class)
.execute(); .execute();
ourLog.info("Expanded: {}",myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expanded)); ourLog.info("Expanded: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expanded));
assertEquals(1, expanded.getExpansion().getContains().size()); assertEquals(1, expanded.getExpansion().getContains().size());
} }
/** /**
* #516 * #516
*/ */
@ -796,7 +795,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
} }
private void validateTermValueSetNotExpanded(String theValueSetName) { private void validateTermValueSetNotExpanded(String theValueSetName) {
runInTransaction(()->{ runInTransaction(() -> {
Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable); Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable);
assertTrue(optionalValueSetByResourcePid.isPresent()); assertTrue(optionalValueSetByResourcePid.isPresent());
@ -814,7 +813,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
} }
private void validateTermValueSetExpandedAndChildren(String theValueSetName, CodeSystem theCodeSystem) { private void validateTermValueSetExpandedAndChildren(String theValueSetName, CodeSystem theCodeSystem) {
runInTransaction(()->{ runInTransaction(() -> {
Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable); Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable);
assertTrue(optionalValueSetByResourcePid.isPresent()); assertTrue(optionalValueSetByResourcePid.isPresent());
@ -906,10 +905,11 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
@Test @Test
public void testValidateCodeOperationByCodeAndSystemInstanceOnType() throws IOException { public void testValidateCodeOperationByCodeAndSystemInstanceOnType() throws IOException {
createLocalCsAndVs(); createLocalCs();
createLocalVsWithIncludeConcept();
String url = ourServerBase + String url = ourServerBase +
"/ValueSet/$validate-code?system=" + "/ValueSet/" + myLocalValueSetId.getIdPart() + "/$validate-code?system=" +
UrlUtil.escapeUrlParam(URL_MY_CODE_SYSTEM) + UrlUtil.escapeUrlParam(URL_MY_CODE_SYSTEM) +
"&code=AA"; "&code=AA";
@ -926,7 +926,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
@Test @Test
public void testValidateCodeOperationByCodeAndSystemInstanceOnInstance() throws IOException { public void testValidateCodeOperationByCodeAndSystemInstanceOnInstance() throws IOException {
createLocalCsAndVs(); createLocalCs();
createLocalVsWithIncludeConcept(); createLocalVsWithIncludeConcept();
String url = ourServerBase + String url = ourServerBase +
@ -953,7 +953,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
Parameters respParam = myClient Parameters respParam = myClient
.operation() .operation()
.onType(ValueSet.class) .onInstance(myExtensionalVsId)
.named("validate-code") .named("validate-code")
.withParameter(Parameters.class, "code", new CodeType("8450-9")) .withParameter(Parameters.class, "code", new CodeType("8450-9"))
.andParameter("system", new UriType("http://acme.org")) .andParameter("system", new UriType("http://acme.org"))
@ -965,6 +965,24 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
} }
@Test
public void testValidateCodeOperationNoValueSetProvided() throws Exception {
loadAndPersistCodeSystemAndValueSet();
try {
myClient
.operation()
.onType(ValueSet.class)
.named("validate-code")
.withParameter(Parameters.class, "code", new CodeType("8450-9"))
.andParameter("system", new UriType("http://acme.org"))
.execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Either ValueSet ID or ValueSet identifier or system and code must be provided. Unable to validate.", e.getMessage());
}
}
@Test @Test
public void testValidateCodeAgainstBuiltInSystem() { public void testValidateCodeAgainstBuiltInSystem() {
Parameters respParam = myClient Parameters respParam = myClient
@ -983,11 +1001,8 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
assertEquals("result", respParam.getParameter().get(0).getName()); assertEquals("result", respParam.getParameter().get(0).getName());
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("message", respParam.getParameter().get(1).getName()); assertEquals("display", respParam.getParameter().get(1).getName());
assertThat(((StringType) respParam.getParameter().get(1).getValue()).getValue(), containsStringIgnoringCase("succeeded")); assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals("Male", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
} }
@Test @Test
@ -1018,7 +1033,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
ourLog.info("Response: {}", response); ourLog.info("Response: {}", response);
} }
HttpGet validateCodeGet = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?code=ChildAA&_pretty=true"); HttpGet validateCodeGet = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?system=http://mycs&code=ChildAA&_pretty=true");
try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet)) { try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet)) {
String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8); String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response: {}", response); ourLog.info("Response: {}", response);
@ -1026,7 +1041,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
assertEquals(true, output.getParameterBool("result")); assertEquals(true, output.getParameterBool("result"));
} }
HttpGet validateCodeGet2 = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?code=FOO&_pretty=true"); HttpGet validateCodeGet2 = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?system=http://mycs&code=FOO&_pretty=true");
try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet2)) { try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet2)) {
String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8); String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response: {}", response); ourLog.info("Response: {}", response);
@ -1070,7 +1085,7 @@ public class ResourceProviderR4ValueSetTest extends BaseResourceProviderR4Test {
TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B"); TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B");
cs.getConcepts().add(parentB); cs.getConcepts().add(parentB);
theTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); theTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table);
return codeSystem; return codeSystem;
} }

View File

@ -10,10 +10,10 @@ import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.entity.TermValueSetConcept; import ca.uhn.fhir.jpa.entity.TermValueSetConcept;
import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation; import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation;
import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum; import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
@ -53,6 +53,7 @@ import java.util.Optional;
import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM; import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_CODE_SYSTEM;
import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_VALUE_SET; import static ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoR4TerminologyTest.URL_MY_VALUE_SET;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.containsStringIgnoringCase; import static org.hamcrest.Matchers.containsStringIgnoringCase;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
@ -60,7 +61,6 @@ import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertSame;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
@ -983,8 +983,8 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
.named("$expand") .named("$expand")
.withNoParameters(Parameters.class) .withNoParameters(Parameters.class)
.returnResourceType(ValueSet.class) .returnResourceType(ValueSet.class)
.execute(); .execute();
ourLog.info("Expanded: {}",myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expanded)); ourLog.info("Expanded: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expanded));
assertEquals(1, expanded.getExpansion().getContains().size()); assertEquals(1, expanded.getExpansion().getContains().size());
// Update the CodeSystem URL and Codes // Update the CodeSystem URL and Codes
@ -1008,12 +1008,11 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
.withNoParameters(Parameters.class) .withNoParameters(Parameters.class)
.returnResourceType(ValueSet.class) .returnResourceType(ValueSet.class)
.execute(); .execute();
ourLog.info("Expanded: {}",myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expanded)); ourLog.info("Expanded: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expanded));
assertEquals(1, expanded.getExpansion().getContains().size()); assertEquals(1, expanded.getExpansion().getContains().size());
} }
/** /**
* #516 * #516
*/ */
@ -1108,7 +1107,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
} }
private void validateTermValueSetNotExpanded(String theValueSetName) { private void validateTermValueSetNotExpanded(String theValueSetName) {
runInTransaction(()->{ runInTransaction(() -> {
Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable); Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable);
assertTrue(optionalValueSetByResourcePid.isPresent()); assertTrue(optionalValueSetByResourcePid.isPresent());
@ -1126,7 +1125,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
} }
private void validateTermValueSetExpandedAndChildren(String theValueSetName, CodeSystem theCodeSystem) { private void validateTermValueSetExpandedAndChildren(String theValueSetName, CodeSystem theCodeSystem) {
runInTransaction(()->{ runInTransaction(() -> {
Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable); Optional<TermValueSet> optionalValueSetByResourcePid = myTermValueSetDao.findByResourcePid(myExtensionalVsIdOnResourceTable);
assertTrue(optionalValueSetByResourcePid.isPresent()); assertTrue(optionalValueSetByResourcePid.isPresent());
@ -1219,9 +1218,10 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
@Test @Test
public void testValidateCodeOperationByCodeAndSystemInstanceOnType() throws IOException { public void testValidateCodeOperationByCodeAndSystemInstanceOnType() throws IOException {
createLocalCsAndVs(); createLocalCsAndVs();
createLocalVsWithIncludeConcept();
String url = ourServerBase + String url = ourServerBase +
"/ValueSet/$validate-code?system=" + "/ValueSet/" + myLocalValueSetId.getIdPart() + "/$validate-code?system=" +
UrlUtil.escapeUrlParam(URL_MY_CODE_SYSTEM) + UrlUtil.escapeUrlParam(URL_MY_CODE_SYSTEM) +
"&code=AA"; "&code=AA";
@ -1265,7 +1265,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
Parameters respParam = myClient Parameters respParam = myClient
.operation() .operation()
.onType(ValueSet.class) .onInstance(myExtensionalVsId)
.named("validate-code") .named("validate-code")
.withParameter(Parameters.class, "code", new CodeType("8450-9")) .withParameter(Parameters.class, "code", new CodeType("8450-9"))
.andParameter("system", new UriType("http://acme.org")) .andParameter("system", new UriType("http://acme.org"))
@ -1297,12 +1297,10 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
assertEquals("result", respParam.getParameter().get(0).getName()); assertEquals("result", respParam.getParameter().get(0).getName());
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("message", respParam.getParameter().get(1).getName()); assertEquals("display", respParam.getParameter().get(1).getName());
assertThat(((StringType) respParam.getParameter().get(1).getValue()).getValue(), containsStringIgnoringCase("succeeded")); assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals("Male", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
} }
// Good code and system, but not in specified valueset // Good code and system, but not in specified valueset
{ {
Parameters respParam = myClient Parameters respParam = myClient
@ -1322,7 +1320,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
assertEquals(false, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); assertEquals(false, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("message", respParam.getParameter().get(1).getName()); assertEquals("message", respParam.getParameter().get(1).getName());
assertThat(((StringType) respParam.getParameter().get(1).getValue()).getValue(), containsStringIgnoringCase("Code not found")); assertEquals("Unknown code 'http://hl7.org/fhir/administrative-gender#male'", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
} }
} }
@ -1352,9 +1350,10 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
try (CloseableHttpResponse status = ourHttpClient.execute(expandGet)) { try (CloseableHttpResponse status = ourHttpClient.execute(expandGet)) {
String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8); String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response: {}", response); ourLog.info("Response: {}", response);
assertThat(response, containsString("<display value=\"Child AA\"/>"));
} }
HttpGet validateCodeGet = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?code=ChildAA&_pretty=true"); HttpGet validateCodeGet = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?system=http://mycs&code=ChildAA&_pretty=true");
try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet)) { try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet)) {
String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8); String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response: {}", response); ourLog.info("Response: {}", response);
@ -1362,7 +1361,32 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
assertEquals(true, output.getParameterBool("result")); assertEquals(true, output.getParameterBool("result"));
} }
HttpGet validateCodeGet2 = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?code=FOO&_pretty=true"); HttpGet validateCodeGet2 = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?system=http://mycs&code=FOO&_pretty=true");
try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet2)) {
String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response: {}", response);
Parameters output = myFhirCtx.newXmlParser().parseResource(Parameters.class, response);
assertEquals(false, output.getParameterBool("result"));
}
// Now do a pre-expansion
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
expandGet = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$expand?_pretty=true");
try (CloseableHttpResponse status = ourHttpClient.execute(expandGet)) {
String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response: {}", response);
}
validateCodeGet = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?system=http://mycs&code=ChildAA&_pretty=true");
try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet)) {
String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response: {}", response);
Parameters output = myFhirCtx.newXmlParser().parseResource(Parameters.class, response);
assertEquals(true, output.getParameterBool("result"));
}
validateCodeGet2 = new HttpGet(ourServerBase + "/ValueSet/" + vsId.getIdPart() + "/$validate-code?system=http://mycs&code=FOO&_pretty=true");
try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet2)) { try (CloseableHttpResponse status = ourHttpClient.execute(validateCodeGet2)) {
String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8); String response = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response: {}", response); ourLog.info("Response: {}", response);
@ -1407,7 +1431,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B"); TermConcept parentB = new TermConcept(cs, "ParentB").setDisplay("Parent B");
cs.getConcepts().add(parentB); cs.getConcepts().add(parentB);
theTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION" , cs, table); theTermCodeSystemStorageSvc.storeNewCodeSystemVersion(new ResourcePersistentId(table.getId()), URL_MY_CODE_SYSTEM, "SYSTEM NAME", "SYSTEM VERSION", cs, table);
return codeSystem; return codeSystem;
} }

View File

@ -2,10 +2,8 @@ package ca.uhn.fhir.jpa.term;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test; import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test;
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.hl7.fhir.dstu3.model.CodeType; import org.hl7.fhir.dstu3.model.CodeType;
import org.hl7.fhir.dstu3.model.Coding; import org.hl7.fhir.dstu3.model.Coding;
@ -13,10 +11,10 @@ import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.PrimitiveType; import org.hl7.fhir.dstu3.model.PrimitiveType;
import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.Type; import org.hl7.fhir.dstu3.model.Type;
import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -30,12 +28,12 @@ import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.awaitility.Awaitility.await; import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.greaterThan;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test { public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test {
@ -228,11 +226,13 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test {
ZipCollectionBuilder files = new ZipCollectionBuilder(); ZipCollectionBuilder files = new ZipCollectionBuilder();
TerminologyLoaderSvcLoincTest.addLoincMandatoryFilesToZip(files); TerminologyLoaderSvcLoincTest.addLoincMandatoryFilesToZip(files);
myLoader.loadLoinc(files.getFiles(), mySrd); myLoader.loadLoinc(files.getFiles(), mySrd);
myTerminologyDeferredStorageSvc.saveDeferred();
myTerminologyDeferredStorageSvc.saveDeferred();
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(null, null, new StringType("10013-1"), new StringType(ITermLoaderSvc.LOINC_URI), null, null, null, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(new UriType("http://loinc.org/vs"), null, new StringType("10013-1"), new StringType(ITermLoaderSvc.LOINC_URI), null, null, null, mySrd);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Found code", result.getMessage()); assertEquals("R' wave amplitude in lead I", result.getDisplay());
} }
@Test @Test
@ -240,11 +240,13 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test {
ZipCollectionBuilder files = new ZipCollectionBuilder(); ZipCollectionBuilder files = new ZipCollectionBuilder();
TerminologyLoaderSvcLoincTest.addLoincMandatoryFilesToZip(files); TerminologyLoaderSvcLoincTest.addLoincMandatoryFilesToZip(files);
myLoader.loadLoinc(files.getFiles(), mySrd); myLoader.loadLoinc(files.getFiles(), mySrd);
myTerminologyDeferredStorageSvc.saveDeferred();
myTerminologyDeferredStorageSvc.saveDeferred();
IFhirResourceDaoValueSet.ValidateCodeResult result = myValueSetDao.validateCode(null, null, new StringType("10013-1-9999999999"), new StringType(ITermLoaderSvc.LOINC_URI), null, null, null, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(new UriType("http://loinc.org/vs"), null, new StringType("10013-1-9999999999"), new StringType(ITermLoaderSvc.LOINC_URI), null, null, null, mySrd);
assertFalse(result.isResult()); assertFalse(result.isOk());
assertEquals("Code not found", result.getMessage()); assertEquals("Unknown code {http://loinc.org}10013-1-9999999999 - Unable to expand ValueSet[http://loinc.org/vs]", result.getMessage());
} }
private Set<String> toExpandedCodes(ValueSet theExpanded) { private Set<String> toExpandedCodes(ValueSet theExpanded) {

View File

@ -3,7 +3,6 @@ package ca.uhn.fhir.jpa.term;
import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.api.model.TranslationRequest; import ca.uhn.fhir.jpa.api.model.TranslationRequest;
import ca.uhn.fhir.jpa.entity.TermConceptMap; import ca.uhn.fhir.jpa.entity.TermConceptMap;
import ca.uhn.fhir.jpa.entity.TermConceptMapGroup; import ca.uhn.fhir.jpa.entity.TermConceptMapGroup;
@ -43,8 +42,8 @@ import static org.junit.jupiter.api.Assertions.fail;
public class TerminologySvcImplR4Test extends BaseTermR4Test { public class TerminologySvcImplR4Test extends BaseTermR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(TerminologySvcImplR4Test.class); private static final Logger ourLog = LoggerFactory.getLogger(TerminologySvcImplR4Test.class);
ValidationOptions optsNoGuess = new ValidationOptions(); ConceptValidationOptions optsNoGuess = new ConceptValidationOptions();
ValidationOptions optsGuess = new ValidationOptions().guessSystem(); ConceptValidationOptions optsGuess = new ConceptValidationOptions().setInferSystem(true);
private IIdType myConceptMapId; private IIdType myConceptMapId;
private void createAndPersistConceptMap() { private void createAndPersistConceptMap() {
@ -1799,42 +1798,37 @@ public class TerminologySvcImplR4Test extends BaseTermR4Test {
myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
IFhirResourceDaoValueSet.ValidateCodeResult result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, null, null); IValidationSupport.CodeValidationResult result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, null, null);
assertNull(result); assertNull(result);
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, "BOGUS", null, null, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, "BOGUS", null, null, null);
assertNull(result); assertFalse(result.isOk());
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, "11378-7", null, null, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, "11378-7", null, null, null);
assertNull(result); assertFalse(result.isOk());
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsGuess, valueSet, null, "11378-7", null, null, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsGuess, valueSet, null, "11378-7", null, null, null);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Validation succeeded", result.getMessage());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsGuess, valueSet, null, "11378-7", "Systolic blood pressure at First encounter", null, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsGuess, valueSet, null, "11378-7", "Systolic blood pressure at First encounter", null, null);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Validation succeeded", result.getMessage());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, "http://acme.org", "11378-7", null, null, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, "http://acme.org", "11378-7", null, null, null);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Validation succeeded", result.getMessage());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
Coding coding = new Coding("http://acme.org", "11378-7", "Systolic blood pressure at First encounter"); Coding coding = new Coding("http://acme.org", "11378-7", "Systolic blood pressure at First encounter");
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, coding, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, coding, null);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Validation succeeded", result.getMessage());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
CodeableConcept codeableConcept = new CodeableConcept(); CodeableConcept codeableConcept = new CodeableConcept();
codeableConcept.addCoding(new Coding("BOGUS", "BOGUS", "BOGUS")); codeableConcept.addCoding(new Coding("BOGUS", "BOGUS", "BOGUS"));
codeableConcept.addCoding(coding); codeableConcept.addCoding(coding);
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, null, codeableConcept); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, null, codeableConcept);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Validation succeeded", result.getMessage());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
@ -1852,43 +1846,38 @@ public class TerminologySvcImplR4Test extends BaseTermR4Test {
myTermSvc.preExpandDeferredValueSetsToTerminologyTables(); myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
IFhirResourceDaoValueSet.ValidateCodeResult result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, null, null); IValidationSupport.CodeValidationResult result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, null, null);
assertNull(result); assertNull(result);
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, "BOGUS", null, null, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, "BOGUS", null, null, null);
assertNull(result); assertFalse(result.isOk());
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, "11378-7", null, null, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, "11378-7", null, null, null);
assertNull(result); assertFalse(result.isOk());
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsGuess, valueSet, null, "11378-7", null, null, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsGuess, valueSet, null, "11378-7", null, null, null);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Validation succeeded", result.getMessage());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsGuess, valueSet, null, "11378-7", "Systolic blood pressure at First encounter", null, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsGuess, valueSet, null, "11378-7", "Systolic blood pressure at First encounter", null, null);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Validation succeeded", result.getMessage());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, "http://acme.org", "11378-7", null, null, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, "http://acme.org", "11378-7", null, null, null);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Validation succeeded", result.getMessage());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
Coding coding = new Coding("http://acme.org", "11378-7", "Systolic blood pressure at First encounter"); Coding coding = new Coding("http://acme.org", "11378-7", "Systolic blood pressure at First encounter");
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, coding, null); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, coding, null);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Validation succeeded", result.getMessage());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
CodeableConcept codeableConcept = new CodeableConcept(); CodeableConcept codeableConcept = new CodeableConcept();
codeableConcept.addCoding(new Coding("BOGUS", "BOGUS", "BOGUS")); codeableConcept.addCoding(new Coding("BOGUS", "BOGUS", "BOGUS"));
codeableConcept.addCoding(coding); codeableConcept.addCoding(coding);
result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, null, codeableConcept); result = myTermSvc.validateCodeIsInPreExpandedValueSet(optsNoGuess, valueSet, null, null, null, null, codeableConcept);
assertTrue(result.isResult()); assertTrue(result.isOk());
assertEquals("Validation succeeded", result.getMessage());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
} }
} }

View File

@ -26,6 +26,10 @@
<code value="8450-9" /> <code value="8450-9" />
<display value="Systolic blood pressure--expiration" /> <display value="Systolic blood pressure--expiration" />
</concept> </concept>
<concept>
<code value="11378-7" />
<display value="Systolic blood pressure at First encounter" />
</concept>
</codeSystem> </codeSystem>
<compose> <compose>
<include> <include>
@ -124,4 +128,4 @@
</concept> </concept>
</include> </include>
</compose> </compose>
</ValueSet> </ValueSet>

View File

@ -6,6 +6,7 @@ import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.util.VersionIndependentConcept; import ca.uhn.fhir.util.VersionIndependentConcept;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.convertors.conv10_50.ValueSet10_50; import org.hl7.fhir.convertors.conv10_50.ValueSet10_50;
@ -26,6 +27,8 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -90,8 +93,11 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
private org.hl7.fhir.r5.model.ValueSet expandValueSetToCanonical(ValidationSupportContext theValidationSupportContext, IBaseResource theValueSetToExpand, @Nullable String theWantSystem, @Nullable String theWantCode) { private org.hl7.fhir.r5.model.ValueSet expandValueSetToCanonical(ValidationSupportContext theValidationSupportContext, IBaseResource theValueSetToExpand, @Nullable String theWantSystem, @Nullable String theWantCode) {
org.hl7.fhir.r5.model.ValueSet expansionR5; org.hl7.fhir.r5.model.ValueSet expansionR5;
switch (myCtx.getVersion().getVersion()) { switch (theValueSetToExpand.getStructureFhirVersionEnum()) {
case DSTU2: case DSTU2: {
expansionR5 = expandValueSetDstu2(theValidationSupportContext, (ca.uhn.fhir.model.dstu2.resource.ValueSet) theValueSetToExpand, theWantSystem, theWantCode);
break;
}
case DSTU2_HL7ORG: { case DSTU2_HL7ORG: {
expansionR5 = expandValueSetDstu2Hl7Org(theValidationSupportContext, (ValueSet) theValueSetToExpand, theWantSystem, theWantCode); expansionR5 = expandValueSetDstu2Hl7Org(theValidationSupportContext, (ValueSet) theValueSetToExpand, theWantSystem, theWantCode);
break; break;
@ -122,11 +128,11 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
@Override @Override
public CodeValidationResult public CodeValidationResult
validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) {
org.hl7.fhir.r5.model.ValueSet expansion = expandValueSetToCanonical(theValidationSupportContext, theValueSet, theCodeSystem, theCode); org.hl7.fhir.r5.model.ValueSet expansion = expandValueSetToCanonical(theValidationSupportContext, theValueSet, theCodeSystem, theCode);
if (expansion == null) { if (expansion == null) {
return null; return null;
} }
return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystem, theCode, expansion); return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, expansion);
} }
@ -174,11 +180,11 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
IBaseResource expansion = valueSetExpansionOutcome.getValueSet(); IBaseResource expansion = valueSetExpansionOutcome.getValueSet();
return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystem, theCode, expansion); return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, expansion);
} }
private CodeValidationResult validateCodeInExpandedValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, IBaseResource theExpansion) { private CodeValidationResult validateCodeInExpandedValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, IBaseResource theExpansion) {
assert theExpansion != null; assert theExpansion != null;
boolean caseSensitive = true; boolean caseSensitive = true;
@ -269,11 +275,20 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
} }
if (codeMatches) { if (codeMatches) {
if (theOptions.isInferSystem() || nextExpansionCode.getSystem().equals(theCodeSystem)) { if (theOptions.isInferSystem() || nextExpansionCode.getSystem().equals(theCodeSystem)) {
return new CodeValidationResult() if (!theOptions.isValidateDisplay() || (isBlank(nextExpansionCode.getDisplay()) || isBlank(theDisplay) || nextExpansionCode.getDisplay().equals(theDisplay))) {
.setCode(theCode) return new CodeValidationResult()
.setDisplay(nextExpansionCode.getDisplay()) .setCode(theCode)
.setCodeSystemName(codeSystemName) .setDisplay(nextExpansionCode.getDisplay())
.setCodeSystemVersion(codeSystemVersion); .setCodeSystemName(codeSystemName)
.setCodeSystemVersion(codeSystemVersion);
} else {
return new CodeValidationResult()
.setSeverity(IssueSeverity.ERROR)
.setDisplay(nextExpansionCode.getDisplay())
.setMessage("Concept Display \"" + theDisplay + "\" does not match expected \"" + nextExpansionCode.getDisplay() + "\"")
.setCodeSystemName(codeSystemName)
.setCodeSystemVersion(codeSystemVersion);
}
} }
} }
} }
@ -316,6 +331,33 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
return (output); return (output);
} }
@Nullable
private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2(ValidationSupportContext theValidationSupportContext, ca.uhn.fhir.model.dstu2.resource.ValueSet theInput, @Nullable String theWantSystem, @Nullable String theWantCode) {
IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG).newJsonParser();
IParser parserHapi = FhirContext.forCached(FhirVersionEnum.DSTU2).newJsonParser();
Function<String, CodeSystem> codeSystemLoader = t -> {
// ca.uhn.fhir.model.dstu2.resource.ValueSet codeSystem = (ca.uhn.fhir.model.dstu2.resource.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t);
ca.uhn.fhir.model.dstu2.resource.ValueSet codeSystem = theInput;
CodeSystem retVal = null;
if (codeSystem != null) {
retVal = new CodeSystem();
retVal.setUrl(codeSystem.getUrl());
addCodesDstu2(codeSystem.getCodeSystem().getConcept(), retVal.getConcept());
}
return retVal;
};
Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader = t -> {
ca.uhn.fhir.model.dstu2.resource.ValueSet valueSet = (ca.uhn.fhir.model.dstu2.resource.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t);
org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource(org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(valueSet));
return ValueSet10_50.convertValueSet(valueSetRi);
};
org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource(org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(theInput));
org.hl7.fhir.r5.model.ValueSet input = ValueSet10_50.convertValueSet(valueSetRi);
org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theValidationSupportContext, input, codeSystemLoader, valueSetLoader, theWantSystem, theWantCode);
return (output);
}
@Override @Override
public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) {
@ -353,6 +395,14 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
} }
} }
private void addCodesDstu2(List<ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystemConcept> theSourceList, List<CodeSystem.ConceptDefinitionComponent> theTargetList) {
for (ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystemConcept nextSource : theSourceList) {
CodeSystem.ConceptDefinitionComponent targetConcept = new CodeSystem.ConceptDefinitionComponent().setCode(nextSource.getCode()).setDisplay(nextSource.getDisplay());
theTargetList.add(targetConcept);
addCodesDstu2(nextSource.getConcept(), targetConcept.getConcept());
}
}
@Nullable @Nullable
private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu3(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.dstu3.model.ValueSet theInput, @Nullable String theWantSystem, @Nullable String theWantCode) { private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu3(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.dstu3.model.ValueSet theInput, @Nullable String theWantSystem, @Nullable String theWantCode) {
Function<String, org.hl7.fhir.r5.model.CodeSystem> codeSystemLoader = t -> { Function<String, org.hl7.fhir.r5.model.CodeSystem> codeSystemLoader = t -> {
@ -452,6 +502,33 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
addCodes(system, codesList, nextCodeList, wantCodes); addCodes(system, codesList, nextCodeList, wantCodes);
ableToHandleCode = true; ableToHandleCode = true;
} }
} else if (theComposeListIsInclude) {
/*
* If we're doing an expansion specifically looking for a single code, that means we're validating that code.
* In the case where we have a ValueSet that explicitly enumerates a collection of codes
* (via ValueSet.compose.include.code) in a code system that is unknown we'll assume the code is valid
* even iof we can't find the CodeSystem. This is a compromise obviously, since it would be ideal for
* CodeSystems to always be known, but realistically there are always going to be CodeSystems that
* can't be supplied because of copyright issues, or because they are grammar based. Allowing a VS to
* enumerate a set of good codes for them is a nice compromise there.
*/
for (org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent next : theComposeList) {
if (Objects.equals(next.getSystem(), theWantSystem)) {
Optional<org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent> matchingEnumeratedConcept = next.getConcept().stream().filter(t -> Objects.equals(t.getCode(), theWantCode)).findFirst();
if (matchingEnumeratedConcept.isPresent()) {
CodeSystem.ConceptDefinitionComponent conceptDefinition = new CodeSystem.ConceptDefinitionComponent()
.addConcept()
.setCode(theWantCode)
.setDisplay(matchingEnumeratedConcept.get().getDisplay());
List<CodeSystem.ConceptDefinitionComponent> codesList = Collections.singletonList(conceptDefinition);
addCodes(system, codesList, nextCodeList, wantCodes);
ableToHandleCode = true;
break;
}
}
}
} }
} }

View File

@ -3,6 +3,7 @@ package org.hl7.fhir.common.hapi.validation.support;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
@ -18,6 +19,8 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import static org.apache.commons.lang3.StringUtils.isBlank;
/** /**
* Simple validation support module that handles profile snapshot generation. * Simple validation support module that handles profile snapshot generation.
* <p> * <p>
@ -75,9 +78,14 @@ public class SnapshotGeneratingValidationSupport implements IValidationSupport {
} }
theValidationSupportContext.getCurrentlyGeneratingSnapshots().add(inputUrl); theValidationSupportContext.getCurrentlyGeneratingSnapshots().add(inputUrl);
IBaseResource base = theValidationSupportContext.getRootValidationSupport().fetchStructureDefinition(inputCanonical.getBaseDefinition()); String baseDefinition = inputCanonical.getBaseDefinition();
if (isBlank(baseDefinition)) {
throw new PreconditionFailedException("StructureDefinition[id=" + inputCanonical.getIdElement().getId() + ", url=" + inputCanonical.getUrl() + "] has no base");
}
IBaseResource base = theValidationSupportContext.getRootValidationSupport().fetchStructureDefinition(baseDefinition);
if (base == null) { if (base == null) {
throw new PreconditionFailedException("Unknown base definition: " + inputCanonical.getBaseDefinition()); throw new PreconditionFailedException("Unknown base definition: " + baseDefinition);
} }
org.hl7.fhir.r5.model.StructureDefinition baseCanonical = (org.hl7.fhir.r5.model.StructureDefinition) converter.toCanonical(base); org.hl7.fhir.r5.model.StructureDefinition baseCanonical = (org.hl7.fhir.r5.model.StructureDefinition) converter.toCanonical(base);
@ -112,6 +120,8 @@ public class SnapshotGeneratingValidationSupport implements IValidationSupport {
return theInput; return theInput;
} catch (BaseServerResponseException e) {
throw e;
} catch (Exception e) { } catch (Exception e) {
throw new InternalErrorException("Failed to generate snapshot", e); throw new InternalErrorException("Failed to generate snapshot", e);
} finally { } finally {

View File

@ -0,0 +1,58 @@
package org.hl7.fhir.common.hapi.validation.support;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class InMemoryTerminologyServerValidationSupportTest {
private InMemoryTerminologyServerValidationSupport mySvc;
private FhirContext myCtx = FhirContext.forR4();
private DefaultProfileValidationSupport myDefaultSupport;
private ValidationSupportChain myChain;
private PrePopulatedValidationSupport myPrePopulated;
@BeforeEach
public void before( ){
mySvc = new InMemoryTerminologyServerValidationSupport(myCtx);
myDefaultSupport = new DefaultProfileValidationSupport(myCtx);
myPrePopulated = new PrePopulatedValidationSupport(myCtx);
myChain = new ValidationSupportChain(mySvc,myPrePopulated, myDefaultSupport);
// Force load
myDefaultSupport.fetchCodeSystem("http://foo");
}
@Test
public void testValidateCodeInUnknownCodeSystemWithEnumeratedValueSet() {
ValueSet vs = new ValueSet();
vs.setUrl("http://vs");
vs
.getCompose()
.addInclude()
.setSystem("http://cs")
.addConcept(new ValueSet.ConceptReferenceComponent(new CodeType("code1")))
.addConcept(new ValueSet.ConceptReferenceComponent(new CodeType("code2")));
myPrePopulated.addValueSet(vs);
ValidationSupportContext valCtx = new ValidationSupportContext(myChain);
ConceptValidationOptions options = new ConceptValidationOptions();
IValidationSupport.CodeValidationResult outcome = myChain.validateCodeInValueSet(valCtx, options, "http://cs", "code1", null, vs);
assertTrue(outcome.isOk());
outcome = myChain.validateCodeInValueSet(valCtx, options, "http://cs", "code99", null, vs);
assertNull(outcome);
}
}