3136 - Provide RemoteTerminologyServiceValidationSupport Implementation For Operation $validate-code (#3142)

* 3136 - Send validate-code requests to an Instance of IValidationSupport instead of directly to the DAO.

* 3136 - Added support for R4 validate-code calls to a Remote Terminology Server.

* Added more Unit Tests.

* Added NULL checks based on code analysis.

* Adding some cleanup code to the Unit Tests since some other unrelated Unit Tests were failing when running in an Azure Pipeline.

* Adding @Ignore to both of my Unit Test classes to try and see if this is the cause of other unrelated Unit Test failures in the Azure Pipeline.

* Commented out ALL CODE in my Unit Tests to see if this fixes the other Unit Tests.

* Added 1 Unit Tester called RemoteTerminologyServiceResourceProviderR4Test to see if it causes other Unit Test failures.

* Added a Unit Tester called ResourceProviderR4RemoteTerminologyTest with the code that creates a RemoteTerminologyServiceValidationSupport instance commented out for now, just to see if this allows all other Unit Tests to pass.

* Added a Unit test back in to verify how the RemoteTerminologyServiceValidationSupport works when inserted into the front of the IValidation chain. Lets see if this will mess up any other Unit Tests in the Azure Pipeline...

* Added an @AfterEach method to remove the RemoteTerminologyServiceValidationSupport instance from the ValidationSupportChain to see if it allows all the other Unit Tests to work as before.

* Added more Unit tests.

* Added more Unit Tests to increase code coverage.

* Added some NULL checks.

* Added a changelog for this feature add.

* Removed the decision logic in the REST APIs and also the isRemoteTerminologyServiceConfigured() method, and letting the ValidationSupportChain figure out how to perform the validate-code Operation.

* Removed a NULL check that was not needed.

* Reverting the previous change where I tried using the IValidationSupportChain for each and every incoming Operation request. Splitting the implementation between local and remote seems to be required at this time.
This commit is contained in:
Kevin Dougan SmileCDR 2021-11-23 10:39:56 -05:00 committed by GitHub
parent 35dedb8628
commit 3c2b4bb397
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 634 additions and 17 deletions

View File

@ -215,6 +215,15 @@ public interface IValidationSupport {
return false;
}
/**
* Returns <code>true</code> if a Remote Terminology Service is currently configured
*
* @return Returns <code>true</code> if a Remote Terminology Service is currently configured
*/
default boolean isRemoteTerminologyServiceConfigured() {
return false;
}
/**
* Fetch the given ValueSet by URL
*/

View File

@ -0,0 +1,4 @@
---
type: add
issue: 3136
title: "Provided a Remote Terminology Service implementation for the $validate-code Operation."

View File

@ -20,11 +20,14 @@ package ca.uhn.fhir.jpa.provider;
* #L%
*/
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.rest.annotation.IdParam;
@ -34,6 +37,7 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
import ca.uhn.fhir.util.ParametersUtil;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.ICompositeType;
@ -42,6 +46,7 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import javax.servlet.http.HttpServletRequest;
@ -56,6 +61,9 @@ public class ValueSetOperationProvider extends BaseJpaProvider {
private DaoRegistry myDaoRegistry;
@Autowired
private ITermReadSvc myTermReadSvc;
@Autowired
@Qualifier(BaseConfig.JPA_VALIDATION_SUPPORT_CHAIN)
private ValidationSupportChain myValidationSupportChain;
public void setDaoConfig(DaoConfig theDaoConfig) {
myDaoConfig = theDaoConfig;
@ -142,24 +150,37 @@ public class ValueSetOperationProvider extends BaseJpaProvider {
RequestDetails theRequestDetails
) {
IValidationSupport.CodeValidationResult result;
startRequest(theServletRequest);
try {
IFhirResourceDaoValueSet<IBaseResource, ICompositeType, ICompositeType> dao = getDao();
IPrimitiveType<String> valueSetIdentifier;
if (theValueSetVersion != null) {
valueSetIdentifier = (IPrimitiveType<String>) getContext().getElementDefinition("uri").newInstance();
valueSetIdentifier.setValue(theValueSetUrl.getValue() + "|" + theValueSetVersion);
// If a Remote Terminology Server has been configured, use it
if (myValidationSupportChain.isRemoteTerminologyServiceConfigured()) {
String theSystemString = (theSystem != null && theSystem.hasValue()) ? theSystem.getValueAsString() : null;
String theCodeString = (theCode != null && theCode.hasValue()) ? theCode.getValueAsString() : null;
String theDisplayString = (theDisplay != null && theDisplay.hasValue()) ? theDisplay.getValueAsString() : null;
String theValueSetUrlString = (theValueSetUrl != null && theValueSetUrl.hasValue()) ?
theValueSetUrl.getValueAsString() : null;
result = myValidationSupportChain.validateCode(new ValidationSupportContext(myValidationSupportChain),
new ConceptValidationOptions(), theSystemString, theCodeString, theDisplayString, theValueSetUrlString);
} else {
valueSetIdentifier = theValueSetUrl;
// Otherwise, use the local DAO layer to validate the code
IFhirResourceDaoValueSet<IBaseResource, ICompositeType, ICompositeType> dao = getDao();
IPrimitiveType<String> valueSetIdentifier;
if (theValueSetUrl != null && theValueSetVersion != null) {
valueSetIdentifier = (IPrimitiveType<String>) getContext().getElementDefinition("uri").newInstance();
valueSetIdentifier.setValue(theValueSetUrl.getValue() + "|" + theValueSetVersion);
} else {
valueSetIdentifier = theValueSetUrl;
}
IPrimitiveType<String> codeSystemIdentifier;
if (theSystem != null && theSystemVersion != null) {
codeSystemIdentifier = (IPrimitiveType<String>) getContext().getElementDefinition("uri").newInstance();
codeSystemIdentifier.setValue(theSystem.getValue() + "|" + theSystemVersion);
} else {
codeSystemIdentifier = theSystem;
}
result = dao.validateCode(valueSetIdentifier, theId, theCode, codeSystemIdentifier, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
}
IPrimitiveType<String> codeSystemIdentifier;
if (theSystemVersion != null) {
codeSystemIdentifier = (IPrimitiveType<String>) getContext().getElementDefinition("uri").newInstance();
codeSystemIdentifier.setValue(theSystem.getValue() + "|" + theSystemVersion);
} else {
codeSystemIdentifier = theSystem;
}
IValidationSupport.CodeValidationResult result = dao.validateCode(valueSetIdentifier, theId, theCode, codeSystemIdentifier, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
return BaseJpaResourceProviderValueSetDstu2.toValidateCodeResult(getContext(), result);
} finally {
endRequest(theServletRequest);

View File

@ -1,13 +1,19 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.BaseJpaResourceProviderValueSetDstu2;
import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType;
@ -17,6 +23,8 @@ import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
@ -43,6 +51,13 @@ import java.util.List;
public class BaseJpaResourceProviderCodeSystemR4 extends JpaResourceProviderR4<CodeSystem> {
@Autowired
@Qualifier(BaseConfig.JPA_VALIDATION_SUPPORT_CHAIN)
private ValidationSupportChain myValidationSupportChain;
@Autowired
protected ITermReadSvcR4 myTermSvc;
/**
* $lookup operation
*/
@ -133,11 +148,35 @@ public class BaseJpaResourceProviderCodeSystemR4 extends JpaResourceProviderR4<C
RequestDetails theRequestDetails
) {
IValidationSupport.CodeValidationResult result = null;
startRequest(theServletRequest);
try {
IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> dao = (IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept>) getDao();
IValidationSupport.CodeValidationResult result = dao.validateCode(theId, theCodeSystemUrl, theVersion, theCode, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
// If a Remote Terminology Server has been configured, use it
if (myValidationSupportChain.isRemoteTerminologyServiceConfigured()) {
String codeSystemUrl = (theCodeSystemUrl != null && theCodeSystemUrl.hasValue()) ?
theCodeSystemUrl.asStringValue() : null;
String code = (theCode != null && theCode.hasValue()) ? theCode.asStringValue() : null;
String display = (theDisplay != null && theDisplay.hasValue()) ? theDisplay.asStringValue() : null;
if (theCoding != null) {
if (theCoding.hasSystem()) {
if (codeSystemUrl != null && !codeSystemUrl.equalsIgnoreCase(theCoding.getSystem())) {
throw new InvalidRequestException("Coding.system '" + theCoding.getSystem() + "' does not equal param url '" + theCodeSystemUrl + "'. Unable to validate-code.");
}
codeSystemUrl = theCoding.getSystem();
code = theCoding.getCode();
display = theCoding.getDisplay();
result = myValidationSupportChain.validateCode(
new ValidationSupportContext(myValidationSupportChain), new ConceptValidationOptions(),
codeSystemUrl, code, display, null);
}
}
} else {
// Otherwise, use the local DAO layer to validate the code
IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> dao = (IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept>) getDao();
result = dao.validateCode(theId, theCodeSystemUrl, theVersion, theCode, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
}
return (Parameters) BaseJpaResourceProviderValueSetDstu2.toValidateCodeResult(getContext(), result);
} finally {
endRequest(theServletRequest);

View File

@ -0,0 +1,257 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType;
import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/*
* This set of Unit Tests simulates the call to a remote server and therefore, only tests the code in the
* {@link org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport#invokeRemoteValidateCode}
* method, before and after it makes that remote client call.
*/
public class RemoteTerminologyServiceResourceProviderR4Test {
private static final String DISPLAY = "DISPLAY";
private static final String CODE_SYSTEM = "CODE_SYS";
private static final String CODE = "CODE";
private static final String VALUE_SET_URL = "http://value.set/url";
private static final String SAMPLE_MESSAGE = "This is a sample message";
private static FhirContext ourCtx = FhirContext.forR4();
private MyCodeSystemProvider myCodeSystemProvider = new MyCodeSystemProvider();
private MyValueSetProvider myValueSetProvider = new MyValueSetProvider();
@RegisterExtension
public RestfulServerExtension myRestfulServerExtension = new RestfulServerExtension(ourCtx, myCodeSystemProvider,
myValueSetProvider);
private RemoteTerminologyServiceValidationSupport mySvc;
@BeforeEach
public void before_ConfigureService() {
String myBaseUrl = "http://localhost:" + myRestfulServerExtension.getPort();
mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, myBaseUrl);
}
@AfterEach
public void after_UnregisterProviders() {
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.ONCE);
myRestfulServerExtension.getRestfulServer().getInterceptorService().unregisterAllInterceptors();
}
@Test
public void testValidateCodeInCodeSystem_BlankCode_ReturnsNull() {
IValidationSupport.CodeValidationResult outcome = mySvc
.validateCode(null, null, CODE_SYSTEM, null, DISPLAY, null);
assertNull(outcome);
}
@Test
public void testValidateCodeInCodeSystem_ProvidingMinimalInputs_ReturnsSuccess() {
createNextCodeSystemReturnParameters(true, null, null);
IValidationSupport.CodeValidationResult outcome = mySvc
.validateCode(null, null, CODE_SYSTEM, CODE, null, null);
assertEquals(CODE, outcome.getCode());
assertEquals(null, outcome.getSeverity());
assertEquals(null, outcome.getMessage());
assertEquals(CODE, myCodeSystemProvider.myLastCode.getCode());
assertEquals(CODE_SYSTEM, myCodeSystemProvider.myLastUrl.getValueAsString());
}
@Test
public void testValidateCodeInCodeSystem_WithMessageValue_ReturnsMessage() {
createNextCodeSystemReturnParameters(true, DISPLAY, SAMPLE_MESSAGE);
IValidationSupport.CodeValidationResult outcome = mySvc
.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, null);
assertEquals(CODE, outcome.getCode());
assertEquals(DISPLAY, outcome.getDisplay());
assertEquals(null, outcome.getSeverity());
assertEquals(null, outcome.getMessage());
assertEquals(CODE, myCodeSystemProvider.myLastCode.getCode());
assertEquals(DISPLAY, myCodeSystemProvider.myLastDisplay.getValue());
assertEquals(CODE_SYSTEM, myCodeSystemProvider.myLastUrl.getValueAsString());
assertEquals(SAMPLE_MESSAGE, myCodeSystemProvider.myNextReturnParams.getParameter("message").toString());
}
@Test
public void testValidateCodeInCodeSystem_AssumeFailure_ReturnsFailureCodeAndFailureMessage() {
createNextCodeSystemReturnParameters(false, null, SAMPLE_MESSAGE);
IValidationSupport.CodeValidationResult outcome = mySvc
.validateCode(null, null, CODE_SYSTEM, CODE, null, null);
assertEquals(IValidationSupport.IssueSeverity.ERROR, outcome.getSeverity());
assertEquals(SAMPLE_MESSAGE, outcome.getMessage());
assertEquals(false, ((BooleanType)myCodeSystemProvider.myNextReturnParams.getParameter("result")).booleanValue());
}
@Test
public void testValidateCodeInValueSet_ProvidingMinimalInputs_ReturnsSuccess() {
createNextValueSetReturnParameters(true, null, null);
IValidationSupport.CodeValidationResult outcome = mySvc
.validateCode(null, null, CODE_SYSTEM, CODE, null, VALUE_SET_URL);
assertEquals(CODE, outcome.getCode());
assertEquals(null, outcome.getSeverity());
assertEquals(null, outcome.getMessage());
assertEquals(CODE, myValueSetProvider.myLastCode.getCode());
assertEquals(VALUE_SET_URL, myValueSetProvider.myLastUrl.getValueAsString());
}
@Test
public void testValidateCodeInValueSet_WithMessageValue_ReturnsMessage() {
createNextValueSetReturnParameters(true, DISPLAY, SAMPLE_MESSAGE);
IValidationSupport.CodeValidationResult outcome = mySvc
.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, VALUE_SET_URL);
assertEquals(CODE, outcome.getCode());
assertEquals(DISPLAY, outcome.getDisplay());
assertEquals(null, outcome.getSeverity());
assertEquals(null, outcome.getMessage());
assertEquals(CODE, myValueSetProvider.myLastCode.getCode());
assertEquals(DISPLAY, myValueSetProvider.myLastDisplay.getValue());
assertEquals(VALUE_SET_URL, myValueSetProvider.myLastUrl.getValueAsString());
assertEquals(SAMPLE_MESSAGE, myValueSetProvider.myNextReturnParams.getParameter("message").toString());
}
private void createNextCodeSystemReturnParameters(boolean theResult, String theDisplay, String theMessage) {
myCodeSystemProvider.myNextReturnParams = new Parameters();
myCodeSystemProvider.myNextReturnParams.addParameter("result", theResult);
myCodeSystemProvider.myNextReturnParams.addParameter("display", theDisplay);
if (theMessage != null) {
myCodeSystemProvider.myNextReturnParams.addParameter("message", theMessage);
}
}
private void createNextValueSetReturnParameters(boolean theResult, String theDisplay, String theMessage) {
myValueSetProvider.myNextReturnParams = new Parameters();
myValueSetProvider.myNextReturnParams.addParameter("result", theResult);
myValueSetProvider.myNextReturnParams.addParameter("display", theDisplay);
if (theMessage != null) {
myValueSetProvider.myNextReturnParams.addParameter("message", theMessage);
}
}
private static class MyCodeSystemProvider implements IResourceProvider {
private UriParam myLastUrlParam;
private List<CodeSystem> myNextReturnCodeSystems;
private int myInvocationCount;
private UriType myLastUrl;
private CodeType myLastCode;
private StringType myLastDisplay;
private Parameters myNextReturnParams;
@Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "url", min = 0, max = 1) UriType theCodeSystemUrl,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay
) {
myInvocationCount++;
myLastUrl = theCodeSystemUrl;
myLastCode = theCode;
myLastDisplay = theDisplay;
return myNextReturnParams;
}
@Search
public List<CodeSystem> find(@RequiredParam(name = "url") UriParam theUrlParam) {
myLastUrlParam = theUrlParam;
assert myNextReturnCodeSystems != null;
return myNextReturnCodeSystems;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return CodeSystem.class;
}
}
private static class MyValueSetProvider implements IResourceProvider {
private Parameters myNextReturnParams;
private List<ValueSet> myNextReturnValueSets;
private UriType myLastUrl;
private CodeType myLastCode;
private int myInvocationCount;
private UriType myLastSystem;
private StringType myLastDisplay;
private ValueSet myLastValueSet;
private UriParam myLastUrlParam;
@Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "url", min = 0, max = 1) UriType theValueSetUrl,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@OperationParam(name = "system", min = 0, max = 1) UriType theSystem,
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay,
@OperationParam(name = "valueSet") ValueSet theValueSet
) {
myInvocationCount++;
myLastUrl = theValueSetUrl;
myLastCode = theCode;
myLastSystem = theSystem;
myLastDisplay = theDisplay;
myLastValueSet = theValueSet;
return myNextReturnParams;
}
@Search
public List<ValueSet> find(@RequiredParam(name = "url") UriParam theUrlParam) {
myLastUrlParam = theUrlParam;
assert myNextReturnValueSets != null;
return myNextReturnValueSets;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return ValueSet.class;
}
}
}

View File

@ -0,0 +1,270 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType;
import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
/*
* This set of Unit Tests instantiates and injects an instance of
* {@link org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport}
* into the ValidationSupportChain, which tests the logic of dynamically selecting the correct Remote Terminology
* implementation. It also exercises the code found in
* {@link org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport#invokeRemoteValidateCode}
*/
public class ResourceProviderR4RemoteTerminologyTest extends BaseResourceProviderR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderR4RemoteTerminologyTest.class);
private static final String DISPLAY = "DISPLAY";
private static final String DISPLAY_BODY_MASS_INDEX = "Body mass index (BMI) [Ratio]";
private static final String CODE_BODY_MASS_INDEX = "39156-5";
private static FhirContext ourCtx = FhirContext.forR4();
private MyCodeSystemProvider myCodeSystemProvider = new MyCodeSystemProvider();
private MyValueSetProvider myValueSetProvider = new MyValueSetProvider();
@RegisterExtension
public RestfulServerExtension myRestfulServerExtension = new RestfulServerExtension(ourCtx, myCodeSystemProvider,
myValueSetProvider);
private RemoteTerminologyServiceValidationSupport mySvc;
@Autowired
@Qualifier(BaseConfig.JPA_VALIDATION_SUPPORT_CHAIN)
private ValidationSupportChain myValidationSupportChain;
@BeforeEach
public void before_addRemoteTerminologySupport() throws Exception {
String baseUrl = "http://localhost:" + myRestfulServerExtension.getPort();
mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx, baseUrl);
myValidationSupportChain.addValidationSupport(0, mySvc);
}
@AfterEach
public void after_removeRemoteTerminologySupport() {
myValidationSupportChain.removeValidationSupport(mySvc);
myRestfulServerExtension.getRestfulServer().getInterceptorService().unregisterAllInterceptors();
}
@Test
public void testValidateCodeOperationOnCodeSystem_ByCodingAndUrlWhereSystemIsDifferent_ThrowsException() {
assertThrows(InvalidRequestException.class, () -> {
Parameters respParam = myClient
.operation()
.onType(CodeSystem.class)
.named(JpaConstants.OPERATION_VALIDATE_CODE)
.withParameter(Parameters.class, "coding", new Coding().setSystem("http://terminology.hl7.org/CodeSystem/v2-0247").setCode("P"))
.andParameter("url", new UriType("http://terminology.hl7.org/CodeSystem/INVALID-CODESYSTEM"))
.execute();
});
}
@Test
public void testValidateCodeOperationOnCodeSystem_ByCodingAndUrl_UsingBuiltInCodeSystems() {
myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>();
myCodeSystemProvider.myNextReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/v2-0247"));
createNextCodeSystemReturnParameters(true, DISPLAY, null);
Parameters respParam = myClient
.operation()
.onType(CodeSystem.class)
.named(JpaConstants.OPERATION_VALIDATE_CODE)
.withParameter(Parameters.class, "coding", new Coding().setSystem("http://terminology.hl7.org/CodeSystem/v2-0247").setCode("P"))
.andParameter("url", new UriType("http://terminology.hl7.org/CodeSystem/v2-0247"))
.execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType)respParam.getParameter("result")).booleanValue());
assertEquals(DISPLAY, respParam.getParameter("display").toString());
}
@Test
public void testValidateCodeOperationOnValueSet_ByUrlAndSystem_UsingBuiltInCodeSystems() {
myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>();
myCodeSystemProvider.myNextReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/list-example-use-codes"));
myValueSetProvider.myNextReturnValueSets = new ArrayList<>();
myValueSetProvider.myNextReturnValueSets.add((ValueSet) new ValueSet().setId("ValueSet/list-example-codes"));
createNextValueSetReturnParameters(true, DISPLAY, null);
Parameters respParam = myClient
.operation()
.onType(ValueSet.class)
.named(JpaConstants.OPERATION_VALIDATE_CODE)
.withParameter(Parameters.class, "code", new CodeType("alerts"))
.andParameter("system", new UriType("http://terminology.hl7.org/CodeSystem/list-example-use-codes"))
.andParameter("url", new UriType("http://hl7.org/fhir/ValueSet/list-example-codes"))
.useHttpGet()
.execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType)respParam.getParameter("result")).booleanValue());
assertEquals(DISPLAY, respParam.getParameter("display").toString());
}
@Test
public void testValidateCodeOperationOnValueSet_ByUrlSystemAndCode() {
myCodeSystemProvider.myNextReturnCodeSystems = new ArrayList<>();
myCodeSystemProvider.myNextReturnCodeSystems.add((CodeSystem) new CodeSystem().setId("CodeSystem/list-example-use-codes"));
myValueSetProvider.myNextReturnValueSets = new ArrayList<>();
myValueSetProvider.myNextReturnValueSets.add((ValueSet) new ValueSet().setId("ValueSet/list-example-codes"));
createNextValueSetReturnParameters(true, DISPLAY_BODY_MASS_INDEX, null);
Parameters respParam = myClient
.operation()
.onType(ValueSet.class)
.named(JpaConstants.OPERATION_VALIDATE_CODE)
.withParameter(Parameters.class, "code", new CodeType(CODE_BODY_MASS_INDEX))
.andParameter("url", new UriType("https://loinc.org"))
.andParameter("system", new UriType("http://loinc.org"))
.execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);
ourLog.info(resp);
assertEquals(true, ((BooleanType)respParam.getParameter("result")).booleanValue());
assertEquals(DISPLAY_BODY_MASS_INDEX, respParam.getParameter("display").toString());
}
private void createNextCodeSystemReturnParameters(boolean theResult, String theDisplay, String theMessage) {
myCodeSystemProvider.myNextReturnParams = new Parameters();
myCodeSystemProvider.myNextReturnParams.addParameter("result", theResult);
myCodeSystemProvider.myNextReturnParams.addParameter("display", theDisplay);
if (theMessage != null) {
myCodeSystemProvider.myNextReturnParams.addParameter("message", theMessage);
}
}
private void createNextValueSetReturnParameters(boolean theResult, String theDisplay, String theMessage) {
myValueSetProvider.myNextReturnParams = new Parameters();
myValueSetProvider.myNextReturnParams.addParameter("result", theResult);
myValueSetProvider.myNextReturnParams.addParameter("display", theDisplay);
if (theMessage != null) {
myValueSetProvider.myNextReturnParams.addParameter("message", theMessage);
}
}
private static class MyCodeSystemProvider implements IResourceProvider {
private UriParam myLastUrlParam;
private List<CodeSystem> myNextReturnCodeSystems;
private int myInvocationCount;
private UriType myLastUrl;
private CodeType myLastCode;
private StringType myLastDisplay;
private Parameters myNextReturnParams;
@Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "url", min = 0, max = 1) UriType theCodeSystemUrl,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay
) {
myInvocationCount++;
myLastUrl = theCodeSystemUrl;
myLastCode = theCode;
myLastDisplay = theDisplay;
return myNextReturnParams;
}
@Search
public List<CodeSystem> find(@RequiredParam(name = "url") UriParam theUrlParam) {
myLastUrlParam = theUrlParam;
assert myNextReturnCodeSystems != null;
return myNextReturnCodeSystems;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return CodeSystem.class;
}
}
private static class MyValueSetProvider implements IResourceProvider {
private Parameters myNextReturnParams;
private List<ValueSet> myNextReturnValueSets;
private UriType myLastUrl;
private CodeType myLastCode;
private int myInvocationCount;
private UriType myLastSystem;
private StringType myLastDisplay;
private ValueSet myLastValueSet;
private UriParam myLastUrlParam;
@Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "url", min = 0, max = 1) UriType theValueSetUrl,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@OperationParam(name = "system", min = 0, max = 1) UriType theSystem,
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay,
@OperationParam(name = "valueSet") ValueSet theValueSet
) {
myInvocationCount++;
myLastUrl = theValueSetUrl;
myLastCode = theCode;
myLastSystem = theSystem;
myLastDisplay = theDisplay;
myLastValueSet = theValueSet;
return myNextReturnParams;
}
@Search
public List<ValueSet> find(@RequiredParam(name = "url") UriParam theUrlParam) {
myLastUrlParam = theUrlParam;
assert myNextReturnValueSets != null;
return myNextReturnValueSets;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return ValueSet.class;
}
}
}

View File

@ -49,6 +49,11 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
super(theFhirContext);
}
public RemoteTerminologyServiceValidationSupport(FhirContext theFhirContext, String theBaseUrl) {
super(theFhirContext);
myBaseUrl = theBaseUrl;
}
@Override
public CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
return invokeRemoteValidateCode(theCodeSystem, theCode, theDisplay, theValueSetUrl, null);

View File

@ -17,6 +17,7 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
@ -171,6 +172,17 @@ public class ValidationSupportChain implements IValidationSupport {
return null;
}
@Override
public boolean isRemoteTerminologyServiceConfigured() {
if (myChain != null) {
Optional<IValidationSupport> remoteTerminologyService = myChain.stream().filter(RemoteTerminologyServiceValidationSupport.class::isInstance).findFirst();
if (remoteTerminologyService.isPresent()) {
return true;
}
}
return false;
}
@Override
public List<IBaseResource> fetchAllConformanceResources() {
List<IBaseResource> retVal = new ArrayList<>();