From ba0589ce7750d2392a36031f7bcd07d3beac658b Mon Sep 17 00:00:00 2001 From: Jens Kristian Villadsen Date: Tue, 13 May 2025 15:39:09 +0200 Subject: [PATCH] Moved terminology config out of being R4 specific. It now applies to all versions (#822) --- .../starter/common/FhirServerConfigR4.java | 60 +--------------- .../terminology/TerminologyConfig.java | 68 +++++++++++++++++++ .../fhir/jpa/starter/ExampleServerR5IT.java | 27 ++++++-- 3 files changed, 89 insertions(+), 66 deletions(-) create mode 100644 src/main/java/ca/uhn/fhir/jpa/starter/terminology/TerminologyConfig.java diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/common/FhirServerConfigR4.java b/src/main/java/ca/uhn/fhir/jpa/starter/common/FhirServerConfigR4.java index 0a4bd73..55dce56 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/common/FhirServerConfigR4.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/common/FhirServerConfigR4.java @@ -1,18 +1,9 @@ package ca.uhn.fhir.jpa.starter.common; -import ca.uhn.fhir.context.FhirContext; -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.config.r4.JpaR4Config; -import ca.uhn.fhir.jpa.starter.AppProperties; import ca.uhn.fhir.jpa.starter.annotations.OnR4Condition; -import ca.uhn.fhir.jpa.starter.common.validation.OnRemoteTerminologyPresent; import ca.uhn.fhir.jpa.starter.cr.StarterCrR4Config; import ca.uhn.fhir.jpa.starter.ips.StarterIpsConfig; -import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; -import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -26,53 +17,4 @@ import org.springframework.context.annotation.Import; ElasticsearchConfig.class, StarterIpsConfig.class }) -public class FhirServerConfigR4 { - - @Bean(name = "myHybridRemoteValidationSupportChain") - @Conditional({OnR4Condition.class, OnRemoteTerminologyPresent.class}) - public IValidationSupport addRemoteValidation( - ValidationSupportChain theValidationSupport, FhirContext theFhirContext, AppProperties theAppProperties) { - var values = theAppProperties.getRemoteTerminologyServicesMap().values(); - - // If the remote terminology service is "*" and is the only one then forward all requests to the remote - // terminology service - if (values.size() == 1 && "*".equalsIgnoreCase(values.iterator().next().getSystem())) { - var remoteSystem = values.iterator().next(); - theValidationSupport.addValidationSupport( - 0, new RemoteTerminologyServiceValidationSupport(theFhirContext, remoteSystem.getUrl())); - return theValidationSupport; - - // If there are multiple remote terminology services, then add each one to the validation chain - } else { - values.forEach((remoteSystem) -> theValidationSupport.addValidationSupport( - 0, new RemoteTerminologyServiceValidationSupport(theFhirContext, remoteSystem.getUrl()) { - @Override - public boolean isCodeSystemSupported( - ValidationSupportContext theValidationSupportContext, String theSystem) { - return remoteSystem.getSystem().equalsIgnoreCase(theSystem); - } - - @Override - public CodeValidationResult validateCode( - ValidationSupportContext theValidationSupportContext, - ConceptValidationOptions theOptions, - String theCodeSystem, - String theCode, - String theDisplay, - String theValueSetUrl) { - if (remoteSystem.getSystem().equalsIgnoreCase(theCodeSystem)) { - return super.validateCode( - theValidationSupportContext, - theOptions, - theCodeSystem, - theCode, - theDisplay, - theValueSetUrl); - } - return null; - } - })); - } - return theValidationSupport; - } -} +public class FhirServerConfigR4 {} diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/terminology/TerminologyConfig.java b/src/main/java/ca/uhn/fhir/jpa/starter/terminology/TerminologyConfig.java new file mode 100644 index 0000000..9b61dde --- /dev/null +++ b/src/main/java/ca/uhn/fhir/jpa/starter/terminology/TerminologyConfig.java @@ -0,0 +1,68 @@ +package ca.uhn.fhir.jpa.starter.terminology; + +import ca.uhn.fhir.context.FhirContext; +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.starter.AppProperties; +import ca.uhn.fhir.jpa.starter.common.StarterJpaConfig; +import ca.uhn.fhir.jpa.starter.common.validation.OnRemoteTerminologyPresent; +import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@Conditional(OnRemoteTerminologyPresent.class) +@Import(StarterJpaConfig.class) +public class TerminologyConfig { + + @Bean(name = "myHybridRemoteValidationSupportChain") + public IValidationSupport addRemoteValidation( + ValidationSupportChain theValidationSupport, FhirContext theFhirContext, AppProperties theAppProperties) { + var values = theAppProperties.getRemoteTerminologyServicesMap().values(); + + // If the remote terminology service is "*" and is the only one then forward all requests to the remote + // terminology service + if (values.size() == 1 && "*".equalsIgnoreCase(values.iterator().next().getSystem())) { + var remoteSystem = values.iterator().next(); + theValidationSupport.addValidationSupport( + 0, new RemoteTerminologyServiceValidationSupport(theFhirContext, remoteSystem.getUrl())); + return theValidationSupport; + + // If there are multiple remote terminology services, then add each one to the validation chain + } else { + values.forEach((remoteSystem) -> theValidationSupport.addValidationSupport( + 0, new RemoteTerminologyServiceValidationSupport(theFhirContext, remoteSystem.getUrl()) { + @Override + public boolean isCodeSystemSupported( + ValidationSupportContext theValidationSupportContext, String theSystem) { + return remoteSystem.getSystem().equalsIgnoreCase(theSystem); + } + + @Override + public CodeValidationResult validateCode( + ValidationSupportContext theValidationSupportContext, + ConceptValidationOptions theOptions, + String theCodeSystem, + String theCode, + String theDisplay, + String theValueSetUrl) { + if (remoteSystem.getSystem().equalsIgnoreCase(theCodeSystem)) { + return super.validateCode( + theValidationSupportContext, + theOptions, + theCodeSystem, + theCode, + theDisplay, + theValueSetUrl); + } + return null; + } + })); + } + return theValidationSupport; + } +} diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR5IT.java b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR5IT.java index e26ad37..f31d003 100644 --- a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR5IT.java +++ b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR5IT.java @@ -11,12 +11,7 @@ import jakarta.websocket.ContainerProvider; import jakarta.websocket.Session; import jakarta.websocket.WebSocketContainer; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r5.model.Bundle; -import org.hl7.fhir.r5.model.Enumerations; -import org.hl7.fhir.r5.model.Observation; -import org.hl7.fhir.r5.model.Patient; -import org.hl7.fhir.r5.model.Subscription; -import org.hl7.fhir.r5.model.SubscriptionTopic; +import org.hl7.fhir.r5.model.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -36,7 +31,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; "spring.datasource.url=jdbc:h2:mem:dbr5", "hapi.fhir.fhir_version=r5", "hapi.fhir.cr_enabled=false", - "hapi.fhir.subscription.websocket_enabled=true" + "hapi.fhir.subscription.websocket_enabled=true", + "hapi.fhir.remote_terminology_service.snomed.system=http://snomed.info/sct", + "hapi.fhir.remote_terminology_service.snomed.url=https://tx.fhir.org/r5" }) public class ExampleServerR5IT { @@ -156,6 +153,22 @@ public class ExampleServerR5IT { ourClient.delete().resourceById(mySubscriptionId).execute(); } + + @Test + void testValidateRemoteTerminology() { + + String testCodeSystem = "http://foo/cs"; + String testValueSet = "http://foo/vs"; + ourClient.create().resource(new CodeSystem().setUrl(testCodeSystem).addConcept(new CodeSystem.ConceptDefinitionComponent().setCode("yes")).addConcept(new CodeSystem.ConceptDefinitionComponent().setCode("no"))).execute(); + ourClient.create().resource(new ValueSet().setUrl(testValueSet).setCompose(new ValueSet.ValueSetComposeComponent().addInclude(new ValueSet.ConceptSetComponent().setSystem(testValueSet)))).execute(); + + Parameters remoteResult = ourClient.operation().onType(ValueSet.class).named("$validate-code").withParameter(Parameters.class, "code", new StringType("22298006")).andParameter("system", new UriType("http://snomed.info/sct")).execute(); + assertEquals(true, ((BooleanType) remoteResult.getParameterValue("result")).getValue()); + assertEquals("Myocardial infarction", ((StringType) remoteResult.getParameterValue("display")).getValue()); + + Parameters localResult = ourClient.operation().onType(CodeSystem.class).named("$validate-code").withParameter(Parameters.class, "url", new UrlType(testCodeSystem)).andParameter("coding", new Coding(testCodeSystem, "yes", null)).execute(); + } + @BeforeEach void beforeEach() {