Skip to content

Commit 8cc6642

Browse files
Merge pull request #795 from jkiddo/feat/remote-terminology-support
Added support for hybrid remote terminology
2 parents 6863e4c + f3ac024 commit 8cc6642

File tree

5 files changed

+141
-13
lines changed

5 files changed

+141
-13
lines changed

src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ public class AppProperties {
106106
private Integer pre_expand_value_sets_max_count = 1000;
107107
private Integer maximum_expansion_size = 1000;
108108

109+
private Map<String, RemoteSystem> remote_terminology_service = null;
110+
109111
public List<String> getCustomInterceptorClasses() {
110112
return custom_interceptor_classes;
111113
}
@@ -723,6 +725,14 @@ public void setMaximum_expansion_size(Integer maximum_expansion_size) {
723725
this.maximum_expansion_size = maximum_expansion_size;
724726
}
725727

728+
public Map<String, RemoteSystem> getRemoteTerminologyServicesMap() {
729+
return remote_terminology_service;
730+
}
731+
732+
public void setRemote_terminology_service(Map<String, RemoteSystem> remote_terminology_service) {
733+
this.remote_terminology_service = remote_terminology_service;
734+
}
735+
726736
public static class Cors {
727737
private Boolean allow_Credentials = true;
728738
private List<String> allowed_origin = List.of("*");
@@ -919,6 +929,27 @@ public void setRequest_tenant_partitioning_mode(boolean theRequest_tenant_partit
919929
}
920930
}
921931

932+
public static class RemoteSystem {
933+
private String system;
934+
private String url;
935+
936+
public String getSystem() {
937+
return system;
938+
}
939+
940+
public void setSystem(String system) {
941+
this.system = system;
942+
}
943+
944+
public String getUrl() {
945+
return url;
946+
}
947+
948+
public void setUrl(String url) {
949+
this.url = url;
950+
}
951+
}
952+
922953
public static class Subscription {
923954

924955
private Boolean resthook_enabled = false;

src/main/java/ca/uhn/fhir/jpa/starter/common/FhirServerConfigR4.java

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
package ca.uhn.fhir.jpa.starter.common;
22

3+
import ca.uhn.fhir.context.FhirContext;
4+
import ca.uhn.fhir.context.support.ConceptValidationOptions;
5+
import ca.uhn.fhir.context.support.IValidationSupport;
6+
import ca.uhn.fhir.context.support.ValidationSupportContext;
37
import ca.uhn.fhir.jpa.config.r4.JpaR4Config;
8+
import ca.uhn.fhir.jpa.starter.AppProperties;
49
import ca.uhn.fhir.jpa.starter.annotations.OnR4Condition;
10+
import ca.uhn.fhir.jpa.starter.common.validation.OnRemoteTerminologyPresent;
511
import ca.uhn.fhir.jpa.starter.cr.StarterCrR4Config;
612
import ca.uhn.fhir.jpa.starter.ips.StarterIpsConfig;
13+
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
14+
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
15+
import org.springframework.context.annotation.Bean;
716
import org.springframework.context.annotation.Conditional;
817
import org.springframework.context.annotation.Configuration;
918
import org.springframework.context.annotation.Import;
@@ -17,4 +26,53 @@
1726
ElasticsearchConfig.class,
1827
StarterIpsConfig.class
1928
})
20-
public class FhirServerConfigR4 {}
29+
public class FhirServerConfigR4 {
30+
31+
@Bean(name = "myHybridRemoteValidationSupportChain")
32+
@Conditional({OnR4Condition.class, OnRemoteTerminologyPresent.class})
33+
public IValidationSupport addRemoteValidation(
34+
ValidationSupportChain theValidationSupport, FhirContext theFhirContext, AppProperties theAppProperties) {
35+
var values = theAppProperties.getRemoteTerminologyServicesMap().values();
36+
37+
// If the remote terminology service is "*" and is the only one then forward all requests to the remote
38+
// terminology service
39+
if (values.size() == 1 && "*".equalsIgnoreCase(values.iterator().next().getSystem())) {
40+
var remoteSystem = values.iterator().next();
41+
theValidationSupport.addValidationSupport(
42+
0, new RemoteTerminologyServiceValidationSupport(theFhirContext, remoteSystem.getUrl()));
43+
return theValidationSupport;
44+
45+
// If there are multiple remote terminology services, then add each one to the validation chain
46+
} else {
47+
values.forEach((remoteSystem) -> theValidationSupport.addValidationSupport(
48+
0, new RemoteTerminologyServiceValidationSupport(theFhirContext, remoteSystem.getUrl()) {
49+
@Override
50+
public boolean isCodeSystemSupported(
51+
ValidationSupportContext theValidationSupportContext, String theSystem) {
52+
return remoteSystem.getSystem().equalsIgnoreCase(theSystem);
53+
}
54+
55+
@Override
56+
public CodeValidationResult validateCode(
57+
ValidationSupportContext theValidationSupportContext,
58+
ConceptValidationOptions theOptions,
59+
String theCodeSystem,
60+
String theCode,
61+
String theDisplay,
62+
String theValueSetUrl) {
63+
if (remoteSystem.getSystem().equalsIgnoreCase(theCodeSystem)) {
64+
return super.validateCode(
65+
theValidationSupportContext,
66+
theOptions,
67+
theCodeSystem,
68+
theCode,
69+
theDisplay,
70+
theValueSetUrl);
71+
}
72+
return null;
73+
}
74+
}));
75+
}
76+
return theValidationSupport;
77+
}
78+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package ca.uhn.fhir.jpa.starter.common.validation;
2+
3+
import ca.uhn.fhir.jpa.starter.AppProperties;
4+
import org.springframework.boot.context.properties.bind.Binder;
5+
import org.springframework.context.annotation.Condition;
6+
import org.springframework.context.annotation.ConditionContext;
7+
import org.springframework.core.type.AnnotatedTypeMetadata;
8+
9+
public class OnRemoteTerminologyPresent implements Condition {
10+
@Override
11+
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
12+
13+
AppProperties config = Binder.get(conditionContext.getEnvironment())
14+
.bind("hapi.fhir", AppProperties.class)
15+
.orElse(null);
16+
if (config == null) return false;
17+
if (config.getRemoteTerminologyServicesMap() == null) return false;
18+
return !config.getRemoteTerminologyServicesMap().isEmpty();
19+
}
20+
}

src/main/resources/application.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,16 @@ hapi:
293293
# max_page_size: 200
294294
# retain_cached_searches_mins: 60
295295
# reuse_cached_search_results_millis: 60000
296+
remote_terminology_service:
297+
# all:
298+
# system: '*'
299+
# url: 'https://tx.fhir.org/r4/'
300+
snomed:
301+
system: 'http://snomed.info/sct'
302+
url: 'https://tx.fhir.org/r4/'
303+
loinc:
304+
system: 'http://loinc.org'
305+
url: 'https://hapi.fhir.org/baseR4/'
296306
tester:
297307
home:
298308
name: Local Tester

src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,7 @@
1818
import org.apache.http.impl.client.HttpClients;
1919
import org.hl7.fhir.instance.model.api.IBaseResource;
2020
import org.hl7.fhir.instance.model.api.IIdType;
21-
import org.hl7.fhir.r4.model.Bundle;
22-
import org.hl7.fhir.r4.model.DateType;
23-
import org.hl7.fhir.r4.model.IdType;
24-
import org.hl7.fhir.r4.model.Measure;
25-
import org.hl7.fhir.r4.model.MeasureReport;
26-
import org.hl7.fhir.r4.model.Observation;
27-
import org.hl7.fhir.r4.model.Parameters;
28-
import org.hl7.fhir.r4.model.Patient;
29-
import org.hl7.fhir.r4.model.Period;
30-
import org.hl7.fhir.r4.model.StringType;
31-
import org.hl7.fhir.r4.model.Subscription;
21+
import org.hl7.fhir.r4.model.*;
3222
import org.junit.jupiter.api.BeforeEach;
3323
import org.junit.jupiter.api.Order;
3424
import org.junit.jupiter.api.Test;
@@ -74,10 +64,14 @@
7464
"hapi.fhir.implementationguides.dk-core.name=hl7.fhir.dk.core",
7565
"hapi.fhir.implementationguides.dk-core.version=1.1.0",
7666
"hapi.fhir.auto_create_placeholder_reference_targets=true",
67+
"hibernate.search.enabled=true",
7768
// Override is currently required when using MDM as the construction of the MDM
7869
// beans are ambiguous as they are constructed multiple places. This is evident
7970
// when running in a spring boot environment
80-
"spring.main.allow-bean-definition-overriding=true"})
71+
"spring.main.allow-bean-definition-overriding=true",
72+
"hapi.fhir.remote_terminology_service.snomed.system=http://snomed.info/sct",
73+
"hapi.fhir.remote_terminology_service.snomed.url=https://tx.fhir.org/r4"
74+
})
8175
class ExampleServerR4IT implements IServerSupport {
8276
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerR4IT.class);
8377
private IGenericClient ourClient;
@@ -358,6 +352,21 @@ void testDiffOperationIsRegistered() {
358352
assertTrue(foundDobChange);
359353
}
360354

355+
@Test
356+
void testValidateRemoteTerminology() {
357+
358+
String testCodeSystem = "http://foo/cs";
359+
String testValueSet = "http://foo/vs";
360+
ourClient.create().resource(new CodeSystem().setUrl(testCodeSystem).addConcept(new CodeSystem.ConceptDefinitionComponent().setCode("yes")).addConcept(new CodeSystem.ConceptDefinitionComponent().setCode("no"))).execute();
361+
ourClient.create().resource(new ValueSet().setUrl(testValueSet).setCompose(new ValueSet.ValueSetComposeComponent().addInclude(new ValueSet.ConceptSetComponent().setSystem(testValueSet)))).execute();
362+
363+
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();
364+
assertEquals(true, ((BooleanType) remoteResult.getParameterValue("result")).getValue());
365+
assertEquals("Myocardial infarction", ((StringType) remoteResult.getParameterValue("display")).getValue());
366+
367+
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();
368+
}
369+
361370
@BeforeEach
362371
void beforeEach() {
363372

0 commit comments

Comments
 (0)