Skip to content

Commit 4ba174a

Browse files
Chris0296barhodes
andauthored
Ensure Authoritative Source Extension is added to Leaf ValueSet during $release (#1009)
* Ensure Authoritative Source Extension is added to Leaf ValueSets retrieved during $release * Move ensureHttps from R4ImportBundleProducer to Uris Utility Class * Sonar Fixes * Fix Typo * add version agnostic method * Fix url value type and cleanup --------- Co-authored-by: Brenin Rhodes <brenin.rhodes@smiledigitalhealth.com>
1 parent f97dedb commit 4ba174a

11 files changed

Lines changed: 1282 additions & 43 deletions

File tree

cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/ecr/r4/R4ImportBundleProducer.java

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44
import ca.uhn.fhir.repository.IRepository;
55
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
66
import java.net.MalformedURLException;
7-
import java.net.URI;
87
import java.net.URISyntaxException;
9-
import java.net.URL;
108
import java.util.*;
119
import java.util.function.Function;
1210
import java.util.function.Predicate;
@@ -15,6 +13,7 @@
1513
import org.hl7.fhir.r4.model.*;
1614
import org.opencds.cqf.fhir.cr.crmi.TransformProperties;
1715
import org.opencds.cqf.fhir.cr.ecr.FhirResourceExistsException;
16+
import org.opencds.cqf.fhir.utility.Uris;
1817
import org.opencds.cqf.fhir.utility.adapter.IAdapterFactory;
1918
import org.opencds.cqf.fhir.utility.adapter.IKnowledgeArtifactAdapter;
2019
import org.opencds.cqf.fhir.utility.search.Searches;
@@ -94,27 +93,6 @@ static void addAuthoritativeSource(ValueSet vs, String url) {
9493
}
9594
}
9695

97-
public static String ensureHttps(String urlString) throws MalformedURLException, URISyntaxException {
98-
URL url = URI.create(urlString).toURL();
99-
100-
// Check if the protocol is already HTTPS
101-
if ("https".equalsIgnoreCase(url.getProtocol())) {
102-
return urlString;
103-
}
104-
105-
// Construct a new URL with the HTTPS protocol
106-
URI httpsUrl = new URI(
107-
"https", // scheme
108-
null, // userinfo
109-
url.getHost(), // host
110-
url.getPort(), // port
111-
url.getFile(), // path
112-
null, // query
113-
null // fragment
114-
);
115-
return httpsUrl.toString();
116-
}
117-
11896
public static List<Bundle.BundleEntryComponent> transformImportBundle(
11997
Bundle parameterBundle, IRepository repository, String appAuthoritativeUrl)
12098
throws FhirResourceExistsException {
@@ -227,7 +205,7 @@ private static void prepareLeafValueSet(ValueSet valueSet) {
227205
String valueSetAuthoritativeSourceUrl = valueSet.getUrl();
228206

229207
try {
230-
valueSetAuthoritativeSourceUrl = ensureHttps(valueSetAuthoritativeSourceUrl);
208+
valueSetAuthoritativeSourceUrl = Uris.ensureHttps(valueSetAuthoritativeSourceUrl);
231209
} catch (URISyntaxException | MalformedURLException e) {
232210
// Do nothing here and let the malformed URL flow through.
233211
}

cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/visitor/ReleaseVisitor.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.opencds.cqf.fhir.cr.visitor;
22

3+
import static org.opencds.cqf.fhir.utility.Resources.newBaseForVersion;
4+
import static org.opencds.cqf.fhir.utility.VersionUtilities.uriTypeForVersion;
35
import static org.opencds.cqf.fhir.utility.adapter.IAdapterFactory.createAdapterForResource;
46

57
import ca.uhn.fhir.context.FhirVersionEnum;
@@ -10,6 +12,8 @@
1012
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
1113
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
1214
import ca.uhn.fhir.util.FhirTerser;
15+
import java.net.MalformedURLException;
16+
import java.net.URISyntaxException;
1317
import java.util.ArrayList;
1418
import java.util.Date;
1519
import java.util.HashMap;
@@ -30,11 +34,13 @@
3034
import org.hl7.fhir.instance.model.api.ICompositeType;
3135
import org.hl7.fhir.instance.model.api.IDomainResource;
3236
import org.opencds.cqf.fhir.cr.common.ExtensionBuilders;
37+
import org.opencds.cqf.fhir.cr.crmi.TransformProperties;
3338
import org.opencds.cqf.fhir.utility.BundleHelper;
3439
import org.opencds.cqf.fhir.utility.Canonicals;
3540
import org.opencds.cqf.fhir.utility.Constants;
3641
import org.opencds.cqf.fhir.utility.PackageHelper;
3742
import org.opencds.cqf.fhir.utility.SearchHelper;
43+
import org.opencds.cqf.fhir.utility.Uris;
3844
import org.opencds.cqf.fhir.utility.adapter.IAdapterFactory;
3945
import org.opencds.cqf.fhir.utility.adapter.IDependencyInfo;
4046
import org.opencds.cqf.fhir.utility.adapter.IEndpointAdapter;
@@ -521,6 +527,7 @@ private Optional<IKnowledgeArtifactAdapter> tryFindLatestDependency(
521527
maybeAdapter = terminologyServerClient
522528
.getLatestValueSetResource(endpoint, reference)
523529
.map(r -> (IKnowledgeArtifactAdapter) createAdapterForResource(r));
530+
maybeAdapter.ifPresent(this::ensureAuthoritativeSourceExtension);
524531
} else if (resourceType != null
525532
&& resourceType.equals(Constants.RESOURCETYPE_CODESYSTEM)
526533
&& latestFromTxServer) {
@@ -544,6 +551,25 @@ private Optional<IKnowledgeArtifactAdapter> tryFindLatestDependency(
544551
return maybeAdapter;
545552
}
546553

554+
private void ensureAuthoritativeSourceExtension(IKnowledgeArtifactAdapter adapter) {
555+
if (adapter.getExtensionByUrl(TransformProperties.authoritativeSourceExtUrl) == null) {
556+
var url = adapter.getUrl();
557+
try {
558+
url = Uris.ensureHttps(url);
559+
} catch (URISyntaxException | MalformedURLException e) {
560+
// Do nothing here and let the malformed URL flow through.
561+
}
562+
var ext = (IBaseExtension<?, ?>) newBaseForVersion("Extension", fhirVersion());
563+
adapter.getModelResolver()
564+
.setValue(
565+
ext,
566+
"url",
567+
uriTypeForVersion(fhirVersion(), TransformProperties.authoritativeSourceExtUrl));
568+
adapter.getModelResolver().setValue(ext, "value", uriTypeForVersion(fhirVersion(), url));
569+
adapter.addExtension(ext);
570+
}
571+
}
572+
547573
private <T extends ICompositeType & IBaseHasExtensions> Optional<IKnowledgeArtifactAdapter> updateComponentAndCache(
548574
T component,
549575
List<IDomainResource> releasedResources,

cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/ecr/r4/R4ImportBundleProducerTest.java

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -55,25 +55,6 @@ public void setUp() {
5555
repository = new InMemoryFhirRepository(fhirContext);
5656
}
5757

58-
@Test
59-
void testEnsureHttpsConvertsHttpToHttps() throws Exception {
60-
String input = "http://example.com/path";
61-
String output = R4ImportBundleProducer.ensureHttps(input);
62-
assertTrue(output.startsWith("https://"));
63-
}
64-
65-
@Test
66-
void testEnsureHttpsPreservesHttps() throws Exception {
67-
String input = "https://secure.com";
68-
String output = R4ImportBundleProducer.ensureHttps(input);
69-
assertEquals(input, output);
70-
}
71-
72-
@Test
73-
void testEnsureHttpsMalformedUrl() {
74-
assertThrows(IllegalArgumentException.class, () -> R4ImportBundleProducer.ensureHttps("://bad-url"));
75-
}
76-
7758
@Test
7859
void testFixIdentifiersAddsUrnOidPrefix() {
7960
var id1 = new org.hl7.fhir.r4.model.Identifier();

cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/visitor/dstu3/ReleaseVisitorTests.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
3232
import org.hl7.fhir.dstu3.model.CodeType;
3333
import org.hl7.fhir.dstu3.model.DateType;
34+
import org.hl7.fhir.dstu3.model.Endpoint;
3435
import org.hl7.fhir.dstu3.model.Extension;
3536
import org.hl7.fhir.dstu3.model.IdType;
3637
import org.hl7.fhir.dstu3.model.Library;
@@ -48,6 +49,7 @@
4849
import org.junit.jupiter.api.BeforeEach;
4950
import org.junit.jupiter.api.Test;
5051
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
52+
import org.opencds.cqf.fhir.cr.crmi.TransformProperties;
5153
import org.opencds.cqf.fhir.cr.visitor.ReleaseVisitor;
5254
import org.opencds.cqf.fhir.cr.visitor.VisitorHelper;
5355
import org.opencds.cqf.fhir.utility.Canonicals;
@@ -59,6 +61,7 @@
5961
import org.opencds.cqf.fhir.utility.adapter.dstu3.AdapterFactory;
6062
import org.opencds.cqf.fhir.utility.client.terminology.ITerminologyServerClient;
6163
import org.opencds.cqf.fhir.utility.dstu3.MetadataResourceHelper;
64+
import org.opencds.cqf.fhir.utility.dstu3.SearchHelper;
6265
import org.opencds.cqf.fhir.utility.repository.InMemoryFhirRepository;
6366
import org.slf4j.event.Level;
6467

@@ -535,6 +538,56 @@ void release_latest_from_tx_server_sets_versions() {
535538
Canonicals.getVersion(leafRelatedArtifact.get().getResource().getReference()));
536539
}
537540

541+
@Test
542+
void release_latest_from_tx_server_ensure_authoritative_source_is_set() {
543+
// Load & prepare data
544+
final var leafOid = "2.16.840.1.113762.1.4.1146.6";
545+
final var authoritativeSource = "https://cts.nlm.nih.gov/fhir/";
546+
var bundle = (Bundle) jsonParser.parseResource(
547+
ReleaseVisitorTests.class.getResourceAsStream("Bundle-small-approved-draft-leaf-unversioned.json"));
548+
repo.transaction(bundle);
549+
removeVersionsFromLibraryAndGrouperAndUpdate(repo, leafOid);
550+
var originalVset = repo.read(ValueSet.class, new IdType("ValueSet/2.16.840.1.113762.1.4.1146.6"));
551+
var library = repo.read(Library.class, new IdType("Library/SpecificationLibrary"))
552+
.copy();
553+
var endpoint = createEndpoint(authoritativeSource);
554+
555+
// Return versioned ValueSet, without Authoritative Source Extension - replicates Terminology Server behavior
556+
var latestVset = originalVset.copy();
557+
latestVset.setVersion("3.0.0");
558+
latestVset.getExtension().removeIf(ext -> ext.getUrl().equals(TransformProperties.authoritativeSourceExtUrl));
559+
var clientMock = mock(ITerminologyServerClient.class, new ReturnsDeepStubs());
560+
when(clientMock.getLatestValueSetResource(any(IEndpointAdapter.class), any()))
561+
.thenReturn(Optional.of(latestVset));
562+
563+
// Perform release
564+
var releaseVisitor = new ReleaseVisitor(repo, clientMock);
565+
var libraryAdapter = new AdapterFactory().createLibrary(library);
566+
var params = parameters(
567+
part("version", new StringType("1.2.7")),
568+
part("versionBehavior", new CodeType("default")),
569+
booleanPart("latestFromTxServer", true),
570+
part("terminologyEndpoint", (Endpoint) endpoint.get()));
571+
libraryAdapter.accept(releaseVisitor, params);
572+
573+
// Retrieve Leaf ValueSet resource & ensure Authoritative Source has been added
574+
var updatedLibrary = repo.read(Library.class, new IdType("Library/SpecificationLibrary"));
575+
var leafRelatedArtifact = updatedLibrary.getRelatedArtifact().stream()
576+
.filter(ra -> ra.getResource().getReference().contains(leafOid))
577+
.findAny();
578+
assertTrue(leafRelatedArtifact.isPresent());
579+
var leaf = (ValueSet) SearchHelper.searchRepositoryByCanonical(
580+
repo, new StringType(leafRelatedArtifact.get().getResource().getReference()));
581+
assertTrue(leaf.hasExtension(TransformProperties.authoritativeSourceExtUrl));
582+
assertTrue(leaf.getExtensionByUrl(TransformProperties.authoritativeSourceExtUrl)
583+
.hasValue());
584+
assertEquals(
585+
authoritativeSource + "ValueSet/" + leafOid,
586+
((UriType) leaf.getExtensionByUrl(TransformProperties.authoritativeSourceExtUrl)
587+
.getValue())
588+
.getValue());
589+
}
590+
538591
private IEndpointAdapter createEndpoint(String authoritativeSource) {
539592
var factory = IAdapterFactory.forFhirVersion(FhirVersionEnum.DSTU3);
540593
var endpoint = factory.createEndpoint(new org.hl7.fhir.dstu3.model.Endpoint());

cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/visitor/r4/ReleaseVisitorTests.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,12 @@
5454
import org.junit.jupiter.api.BeforeEach;
5555
import org.junit.jupiter.api.Test;
5656
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
57+
import org.opencds.cqf.fhir.cr.crmi.TransformProperties;
5758
import org.opencds.cqf.fhir.cr.visitor.ReleaseVisitor;
5859
import org.opencds.cqf.fhir.cr.visitor.VisitorHelper;
5960
import org.opencds.cqf.fhir.utility.Canonicals;
6061
import org.opencds.cqf.fhir.utility.Constants;
62+
import org.opencds.cqf.fhir.utility.SearchHelper;
6163
import org.opencds.cqf.fhir.utility.adapter.IAdapterFactory;
6264
import org.opencds.cqf.fhir.utility.adapter.IEndpointAdapter;
6365
import org.opencds.cqf.fhir.utility.adapter.IKnowledgeArtifactAdapter;
@@ -562,6 +564,54 @@ void release_latest_from_tx_server_sets_versions() {
562564
Canonicals.getVersion(leafRelatedArtifact.get().getResource()));
563565
}
564566

567+
@Test
568+
void release_latest_from_tx_server_ensure_authoritative_source_is_set() {
569+
// Load & prepare data
570+
final var leafOid = "2.16.840.1.113762.1.4.1146.6";
571+
final var authoritativeSource = "https://cts.nlm.nih.gov/fhir/";
572+
var bundle = (Bundle) jsonParser.parseResource(
573+
ReleaseVisitorTests.class.getResourceAsStream("Bundle-small-approved-draft-leaf-unversioned.json"));
574+
repo.transaction(bundle);
575+
removeVersionsFromLibraryAndGrouperAndUpdate(repo, leafOid);
576+
var originalVset = repo.read(ValueSet.class, new IdType("ValueSet/2.16.840.1.113762.1.4.1146.6"));
577+
var library = repo.read(Library.class, new IdType("Library/SpecificationLibrary"))
578+
.copy();
579+
var endpoint = createEndpoint(authoritativeSource);
580+
581+
// Return versioned ValueSet, without Authoritative Source Extension - replicates Terminology Server behavior
582+
var latestVset = originalVset.copy();
583+
latestVset.setVersion("3.0.0");
584+
latestVset.getExtension().removeIf(ext -> ext.getUrl().equals(TransformProperties.authoritativeSourceExtUrl));
585+
var clientMock = mock(ITerminologyServerClient.class, new ReturnsDeepStubs());
586+
when(clientMock.getLatestValueSetResource(any(IEndpointAdapter.class), any()))
587+
.thenReturn(Optional.of(latestVset));
588+
589+
// Perform Release
590+
var releaseVisitor = new ReleaseVisitor(repo, clientMock);
591+
var libraryAdapter = new AdapterFactory().createLibrary(library);
592+
var params = parameters(
593+
part("version", new StringType("1.2.7")),
594+
part("versionBehavior", new CodeType("default")),
595+
booleanPart("latestFromTxServer", true),
596+
part("terminologyEndpoint", (org.hl7.fhir.r4.model.Endpoint) endpoint.get()));
597+
libraryAdapter.accept(releaseVisitor, params);
598+
599+
// Retrieve Leaf ValueSet resource & ensure Authoritative Source has been added
600+
var updatedLibrary = repo.read(Library.class, new IdType("Library/SpecificationLibrary"));
601+
var leafRelatedArtifact = updatedLibrary.getRelatedArtifact().stream()
602+
.filter(ra -> ra.getResource().contains(leafOid))
603+
.findAny();
604+
assertTrue(leafRelatedArtifact.isPresent());
605+
var leaf = (ValueSet) SearchHelper.searchRepositoryByCanonical(
606+
repo, leafRelatedArtifact.get().getResourceElement());
607+
assertTrue(leaf.hasExtension(TransformProperties.authoritativeSourceExtUrl));
608+
assertTrue(leaf.getExtensionByUrl(TransformProperties.authoritativeSourceExtUrl)
609+
.hasValue());
610+
assertEquals(
611+
authoritativeSource + "ValueSet/" + leafOid,
612+
leaf.getExtensionString(TransformProperties.authoritativeSourceExtUrl));
613+
}
614+
565615
private IEndpointAdapter createEndpoint(String authoritativeSource) {
566616
var factory = IAdapterFactory.forFhirVersion(FhirVersionEnum.R4);
567617
var endpoint = factory.createEndpoint(new org.hl7.fhir.r4.model.Endpoint());

cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/visitor/r5/ReleaseVisitorTests.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.hl7.fhir.r5.model.CanonicalType;
3535
import org.hl7.fhir.r5.model.CodeType;
3636
import org.hl7.fhir.r5.model.DateType;
37+
import org.hl7.fhir.r5.model.Endpoint;
3738
import org.hl7.fhir.r5.model.Extension;
3839
import org.hl7.fhir.r5.model.IdType;
3940
import org.hl7.fhir.r5.model.Library;
@@ -49,10 +50,12 @@
4950
import org.junit.jupiter.api.BeforeEach;
5051
import org.junit.jupiter.api.Test;
5152
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
53+
import org.opencds.cqf.fhir.cr.crmi.TransformProperties;
5254
import org.opencds.cqf.fhir.cr.visitor.ReleaseVisitor;
5355
import org.opencds.cqf.fhir.cr.visitor.VisitorHelper;
5456
import org.opencds.cqf.fhir.utility.Canonicals;
5557
import org.opencds.cqf.fhir.utility.Constants;
58+
import org.opencds.cqf.fhir.utility.SearchHelper;
5659
import org.opencds.cqf.fhir.utility.adapter.IAdapterFactory;
5760
import org.opencds.cqf.fhir.utility.adapter.IEndpointAdapter;
5861
import org.opencds.cqf.fhir.utility.adapter.IKnowledgeArtifactAdapter;
@@ -538,6 +541,54 @@ void release_latest_from_tx_server_sets_versions() {
538541
Canonicals.getVersion(leafRelatedArtifact.get().getResource()));
539542
}
540543

544+
@Test
545+
void release_latest_from_tx_server_ensure_authoritative_source_is_set() {
546+
// Load & prepare data
547+
final var leafOid = "2.16.840.1.113762.1.4.1146.6";
548+
final var authoritativeSource = "https://cts.nlm.nih.gov/fhir/";
549+
var bundle = (Bundle) jsonParser.parseResource(
550+
ReleaseVisitorTests.class.getResourceAsStream("Bundle-small-approved-draft-leaf-unversioned.json"));
551+
repo.transaction(bundle);
552+
removeVersionsFromLibraryAndGrouperAndUpdate(repo, leafOid);
553+
var originalVset = repo.read(ValueSet.class, new IdType("ValueSet/2.16.840.1.113762.1.4.1146.6"));
554+
var library = repo.read(Library.class, new IdType("Library/SpecificationLibrary"))
555+
.copy();
556+
var endpoint = createEndpoint(authoritativeSource);
557+
558+
// Return versioned ValueSet, without Authoritative Source Extension - replicate Terminology Server behavior
559+
var latestVset = originalVset.copy();
560+
latestVset.setVersion("3.0.0");
561+
latestVset.getExtension().removeIf(ext -> ext.getUrl().equals(TransformProperties.authoritativeSourceExtUrl));
562+
var clientMock = mock(ITerminologyServerClient.class, new ReturnsDeepStubs());
563+
when(clientMock.getLatestValueSetResource(any(IEndpointAdapter.class), any()))
564+
.thenReturn(Optional.of(latestVset));
565+
566+
// Perform release
567+
var releaseVisitor = new ReleaseVisitor(repo, clientMock);
568+
var libraryAdapter = new AdapterFactory().createLibrary(library);
569+
var params = parameters(
570+
part("version", new StringType("1.2.7")),
571+
part("versionBehavior", new CodeType("default")),
572+
booleanPart("latestFromTxServer", true),
573+
part("terminologyEndpoint", (Endpoint) endpoint.get()));
574+
libraryAdapter.accept(releaseVisitor, params);
575+
var updatedLibrary = repo.read(Library.class, new IdType("Library/SpecificationLibrary"));
576+
577+
// Retrieve Leaf ValueSet resource & ensure Authoritative Source has been added
578+
var leafRelatedArtifact = updatedLibrary.getRelatedArtifact().stream()
579+
.filter(ra -> ra.getResource().contains(leafOid))
580+
.findAny();
581+
assertTrue(leafRelatedArtifact.isPresent());
582+
var leaf = (ValueSet) SearchHelper.searchRepositoryByCanonical(
583+
repo, leafRelatedArtifact.get().getResourceElement());
584+
assertTrue(leaf.hasExtension(TransformProperties.authoritativeSourceExtUrl));
585+
assertTrue(leaf.getExtensionByUrl(TransformProperties.authoritativeSourceExtUrl)
586+
.hasValue());
587+
assertEquals(
588+
authoritativeSource + "ValueSet/" + leafOid,
589+
leaf.getExtensionString(TransformProperties.authoritativeSourceExtUrl));
590+
}
591+
541592
private IEndpointAdapter createEndpoint(String authoritativeSource) {
542593
var factory = IAdapterFactory.forFhirVersion(FhirVersionEnum.R5);
543594
var endpoint = factory.createEndpoint(new org.hl7.fhir.r5.model.Endpoint());

0 commit comments

Comments
 (0)