Skip to content

Commit

Permalink
Merge $package operation from CQF-Ruler with ValueSet expansion capab…
Browse files Browse the repository at this point in the history
…ility (#455)

* Merge $package operation from CQF-Ruler with ValueSet expansion capability

* Update URL constant and test data

* Refactor - simplify functions and fix typos

* Refactor

-make local methods protected
-throw unsupported error when invalid fhir version supplied
-move constants
-remove ResourceClassMapHelper
-change checkIfValueSetNeedsCondition to reflect removal of ResourceClassMapHelper

* Remove unnecessary search for resource
  • Loading branch information
Chris0296 committed May 14, 2024
1 parent aaa2636 commit c0f8cd4
Show file tree
Hide file tree
Showing 26 changed files with 35,311 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import ca.uhn.fhir.model.api.Tag;
import com.google.common.collect.ImmutableMap;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

public class Constants {
Expand Down Expand Up @@ -112,4 +115,25 @@ private Constants() {}
public static final String APPLY_PARAMETER_DATA_ENDPOINT = "dataEndpoint";
public static final String APPLY_PARAMETER_CONTENT_ENDPOINT = "contentEndpoint";
public static final String APPLY_PARAMETER_TERMINOLOGY_ENDPOINT = "terminologyEndpoint";
public static final String US_PH_CONTEXT_URL = "http:https://hl7.org/fhir/us/ecr/CodeSystem/us-ph-usage-context";
public static final String VALUE_SET_CONDITION_CODE = "focus";
public static final String VALUE_SET_PRIORITY_CODE = "priority";
public static final String VALUE_SET_CONDITION_URL =
"http:https://aphl.org/fhir/vsm/StructureDefinition/vsm-valueset-condition";
public static final String VALUE_SET_PRIORITY_URL =
"http:https://aphl.org/fhir/vsm/StructureDefinition/vsm-valueset-priority";
public static final String LIBRARY_TYPE = "http:https://terminology.hl7.org/CodeSystem/library-type";
public static final String ASSET_COLLECTION = "asset-collection";
public static final String EXPANSION_PARAMETERS_URL =
"http:https://hl7.org/fhir/StructureDefinition/cqf-expansionParameters";

// can't use List.of for Android 26 compatibility
public static final List<String> PRESERVED_EXTENSION_URLS =
Collections.unmodifiableList(Arrays.asList(VALUE_SET_PRIORITY_URL, VALUE_SET_CONDITION_URL));

public static final String AUTHORITATIVE_SOURCE_URL =
"http:https://hl7.org/fhir/StructureDefinition/valueset-authoritativeSource";
public static final String VSM_WORKFLOW_CODES_CODE_SYSTEM_URL =
"http:https://aphl.org/fhir/vsm/CodeSystem/vsm-workflow-codes";
public static final String VSM_VALUE_SET_TAG_VSM_AUTHORED_CODE = "vsm-authored";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.opencds.cqf.fhir.utility;

import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.Description;
import org.hl7.fhir.r4.model.Endpoint;
import org.hl7.fhir.r4.model.StringType;

public class EndpointCredentials extends Endpoint {

@Child(
name = "vsacUsername",
type = {StringType.class},
order = 11,
min = 0,
max = 1,
modifier = false,
summary = true)
@Description(
shortDefinition = "A name that this endpoint can be identified by",
formalDefinition = "A friendly name that this endpoint can be referred to with.")
protected StringType vsacUsername;

@Child(
name = "apiKey",
type = {StringType.class},
order = 11,
min = 0,
max = 1,
modifier = false,
summary = true)
@Description(
shortDefinition = "A name that this endpoint can be identified by",
formalDefinition = "A friendly name that this endpoint can be referred to with.")
protected StringType apiKey;

public StringType getVsacUsername() {
return vsacUsername;
}

public void setVsacUsername(StringType vsacUsername) {
this.vsacUsername = vsacUsername;
}

public StringType getApiKey() {
return apiKey;
}

public void setApiKey(StringType apiKey) {
this.apiKey = apiKey;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@
import ca.uhn.fhir.rest.client.api.IClientInterceptor;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.AdditionalRequestHeadersInterceptor;
import ca.uhn.fhir.rest.client.interceptor.BasicAuthInterceptor;
import ca.uhn.fhir.rest.client.interceptor.BearerTokenAuthInterceptor;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;

/**
* This class provides utility functions for creating IGenericClients and setting up authentication
Expand Down Expand Up @@ -245,6 +249,20 @@ public static void registerBearerTokenAuth(IGenericClient client, String token)
}
}

public static void registerAdditionalRequestHeadersAuth(IGenericClient client, String username, String apiKey) {
checkNotNull(client, "client is required");

if (username != null && apiKey != null) {
String authString = StringUtils.join(
"Basic ",
Base64.getEncoder()
.encodeToString(
StringUtils.join(username, ":", apiKey).getBytes(StandardCharsets.UTF_8)));
AdditionalRequestHeadersInterceptor authInterceptor = new AdditionalRequestHeadersInterceptor();
authInterceptor.addHeaderValue("Authorization", authString);
}
}

/**
* Parses a list of headers into their constituent parts. Used to prep the headers for
* registration with HeaderInjectionInterceptors
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package org.opencds.cqf.fhir.utility.client;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import java.util.Objects;
import org.opencds.cqf.fhir.utility.Canonicals;

public class TerminologyServerClient {

private final FhirContext ctx;

public TerminologyServerClient(FhirContext ctx) {
this.ctx = ctx;
}

public org.hl7.fhir.dstu3.model.ValueSet expand(
org.hl7.fhir.dstu3.model.ValueSet valueSet,
String authoritativeSource,
org.hl7.fhir.dstu3.model.Parameters expansionParameters,
String username,
String apiKey) {
IGenericClient fhirClient = ctx.newRestfulGenericClient(getAuthoritativeSourceBase(authoritativeSource));
Clients.registerAdditionalRequestHeadersAuth(fhirClient, username, apiKey);

// Invoke by Value Set ID
return fhirClient
.operation()
.onInstance(valueSet.getId())
.named("$expand")
.withParameters(expansionParameters)
.returnResourceType(org.hl7.fhir.dstu3.model.ValueSet.class)
.execute();
}

public org.hl7.fhir.r4.model.ValueSet expand(
org.hl7.fhir.r4.model.ValueSet valueSet,
String authoritativeSource,
org.hl7.fhir.r4.model.Parameters expansionParameters,
String username,
String apiKey) {
IGenericClient fhirClient = ctx.newRestfulGenericClient(getAuthoritativeSourceBase(authoritativeSource));
Clients.registerAdditionalRequestHeadersAuth(fhirClient, username, apiKey);

// Invoke by Value Set ID
return fhirClient
.operation()
.onInstance(valueSet.getId())
.named("$expand")
.withParameters(expansionParameters)
.returnResourceType(org.hl7.fhir.r4.model.ValueSet.class)
.execute();
}

public org.hl7.fhir.r5.model.ValueSet expand(
org.hl7.fhir.r5.model.ValueSet valueSet,
String authoritativeSource,
org.hl7.fhir.r5.model.Parameters expansionParameters,
String username,
String apiKey) {
IGenericClient fhirClient = ctx.newRestfulGenericClient(getAuthoritativeSourceBase(authoritativeSource));
Clients.registerAdditionalRequestHeadersAuth(fhirClient, username, apiKey);

// Invoke by Value Set ID
return fhirClient
.operation()
.onInstance(valueSet.getId())
.named("$expand")
.withParameters(expansionParameters)
.returnResourceType(org.hl7.fhir.r5.model.ValueSet.class)
.execute();
}

// Strips resource and id from the authoritative source URL, these are not needed as the client constructs the URL.
// Converts http URLs to https
private String getAuthoritativeSourceBase(String authoritativeSource) {
authoritativeSource = authoritativeSource.substring(
0,
authoritativeSource.indexOf(Objects.requireNonNull(Canonicals.getResourceType(authoritativeSource))));
if (authoritativeSource.startsWith("http:https://")) {
authoritativeSource = authoritativeSource.replaceFirst("http:https://", "https://");
}
return authoritativeSource;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,7 @@ public IBaseResource visit(LibraryAdapter library, Repository repository, IBaseP
|| (endpointUri.isPresent()
&& !StringUtils.isBlank(endpointUri.get())
&& !endpointUri.get().isEmpty())
|| endpoint.isPresent()
|| terminologyEndpoint.isPresent()) {
|| endpoint.isPresent()) {
throw new NotImplementedOperationException(
"This repository is not implementing custom Content and Terminology endpoints at this time");
}
Expand Down Expand Up @@ -114,13 +113,54 @@ public IBaseResource visit(LibraryAdapter library, Repository repository, IBaseP
}
setCorrectBundleType(count, offset, packagedBundle, fhirVersion);
pageBundleBasedOnCountAndOffset(count, offset, packagedBundle);
handleValueSetReferenceExtensions(resource, packagedBundle, repository, terminologyEndpoint);
return packagedBundle;

// DependencyInfo --document here that there is a need for figuring out how to determine which package the
// dependency is in.
// what is dependency, where did it originate? potentially the package?
}

protected void handleValueSetReferenceExtensions(
IDomainResource resource,
IBaseBundle packagedBundle,
Repository repository,
Optional<IBaseResource> terminologyEndpoint) {
switch (resource.getStructureFhirVersionEnum()) {
case DSTU3:
org.opencds.cqf.fhir.utility.visitor.dstu3.KnowledgeArtifactPackageVisitor packageVisitorDstu3 =
new org.opencds.cqf.fhir.utility.visitor.dstu3.KnowledgeArtifactPackageVisitor();
packageVisitorDstu3.handleValueSetReferenceExtensions(
(org.hl7.fhir.dstu3.model.MetadataResource) resource,
((org.hl7.fhir.dstu3.model.Bundle) packagedBundle).getEntry(),
repository,
terminologyEndpoint);
break;
case R4:
org.opencds.cqf.fhir.utility.visitor.r4.KnowledgeArtifactPackageVisitor packageVisitorR4 =
new org.opencds.cqf.fhir.utility.visitor.r4.KnowledgeArtifactPackageVisitor();
packageVisitorR4.handleValueSetReferenceExtensions(
(org.hl7.fhir.r4.model.MetadataResource) resource,
((org.hl7.fhir.r4.model.Bundle) packagedBundle).getEntry(),
repository,
terminologyEndpoint);
break;
case R5:
org.opencds.cqf.fhir.utility.visitor.r5.KnowledgeArtifactPackageVisitor packageVisitorR5 =
new org.opencds.cqf.fhir.utility.visitor.r5.KnowledgeArtifactPackageVisitor();
packageVisitorR5.handleValueSetReferenceExtensions(
(org.hl7.fhir.r5.model.MetadataResource) resource,
((org.hl7.fhir.r5.model.Bundle) packagedBundle).getEntry(),
repository,
terminologyEndpoint);
break;
default:
throw new IllegalArgumentException(String.format(
"Unsupported FHIR version: %s",
resource.getStructureFhirVersionEnum().getFhirVersionString()));
}
}

void recursivePackage(
IDomainResource resource,
IBaseBundle bundle,
Expand Down
Loading

0 comments on commit c0f8cd4

Please sign in to comment.