Skip to content

Commit

Permalink
updates to package and expand: (#506)
Browse files Browse the repository at this point in the history
* updates to package and expand:
- refactor out "tryGetLatest"
- update expandHelper to pull from tx server or repository as applicable
- possible bug in expansion handling

* spotless and cleanup

* update tests including added test for expansion params

* update to ensure grouper url/version exp params don't affect children

* make it an error not log

* better message

---------

Co-authored-by: [email protected] <[email protected]>
  • Loading branch information
TahaAttari and [email protected] authored Aug 22, 2024
1 parent bc0b71f commit 23a78d7
Show file tree
Hide file tree
Showing 7 changed files with 315 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.opencds.cqf.fhir.api.Repository;
import org.opencds.cqf.fhir.utility.adapter.EndpointAdapter;
import org.opencds.cqf.fhir.utility.adapter.ParametersAdapter;
import org.opencds.cqf.fhir.utility.adapter.ValueSetAdapter;
import org.opencds.cqf.fhir.utility.client.TerminologyServerClient;
import org.opencds.cqf.fhir.utility.visitor.VisitorHelper;

public class ExpandHelper {

private final FhirContext fhirContext;
private final TerminologyServerClient terminologyServerClient;

Expand All @@ -30,21 +33,21 @@ public void expandValueSet(
ParametersAdapter expansionParameters,
Optional<EndpointAdapter> terminologyEndpoint,
List<ValueSetAdapter> valueSets,
List<String> expandedList) {
List<String> expandedList,
Repository repository) {
// Have we already expanded this ValueSet?
if (expandedList.contains(valueSet.getUrl())) {
// Nothing to do here
return;
}

// Gather the Terminology Service from the valueSet's authoritativeSourceUrl.
@SuppressWarnings("unchecked")
var authoritativeSourceUrl = valueSet.getExtension().stream()
.filter(e -> e.getUrl().equals(Constants.AUTHORITATIVE_SOURCE_URL))
.findFirst()
.map(url -> ((IPrimitiveType<String>) url.getValue()).getValueAsString())
.map(url -> TerminologyServerClient.getAddressBase(url, fhirContext))
.orElse(null);

// If terminologyEndpoint exists and we have no authoritativeSourceUrl or the authoritativeSourceUrl matches the
// terminologyEndpoint address then we will use the terminologyEndpoint for expansion
if (terminologyEndpoint.isPresent()
Expand Down Expand Up @@ -74,36 +77,79 @@ else if (valueSet.hasGroupingCompose()) {
var split = reference.split("\\|");
var url = split.length == 1 ? reference : split[0];
var version = split.length == 1 ? null : split[1];
var vs = valueSets.stream()
var includedVS = valueSets.stream()
.filter(v -> v.getUrl().equals(url)
&& (version == null || v.getVersion().equals(version)))
.findFirst()
.orElse(null);
// Expand the ValueSet if we haven't already
if (!expandedList.contains(url)) {
expandValueSet(vs, expansionParameters, terminologyEndpoint, valueSets, expandedList);
}
getCodesInExpansion(fhirContext, vs.get()).forEach(code -> {
// Add the code if not already present
var existingCodes = getCodesInExpansion(fhirContext, expansion);
if (existingCodes != null
&& existingCodes.stream()
.noneMatch(expandedCode -> code.getSystem().equals(expandedCode.getSystem())
&& code.getCode().equals(expandedCode.getCode())
&& (StringUtils.isEmpty(code.getVersion())
|| code.getVersion().equals(expandedCode.getVersion())))) {
try {
addCodeToExpansion(fhirContext, expansion, code);
} catch (Exception ex) {
throw new UnprocessableEntityException(String.format(
"Encountered exception attempting to expand ValueSet %s: %s",
vs.get().getId(), ex.getMessage()));
.orElseGet(() -> {
if (terminologyEndpoint.isPresent()) {
return terminologyServerClient
.getResource(
terminologyEndpoint.get(),
reference,
valueSet.get().getStructureFhirVersionEnum())
.map(r -> (ValueSetAdapter) createAdapterForResource(r))
.orElse(null);
} else {
return VisitorHelper.tryGetLatestVersion(reference, repository)
.map(a -> (ValueSetAdapter) a)
.orElse(null);
}
});
if (includedVS != null) {
// Expand the ValueSet if we haven't already
if (!expandedList.contains(url)) {
// update url and version exp params for child expansions
var childExpParams = (ParametersAdapter) createAdapterForResource(expansionParameters.copy());
var urlParam = childExpParams.getParameter(TerminologyServerClient.urlParamName);
if (urlParam != null) {
var ind = childExpParams.getParameter().indexOf(urlParam);
childExpParams.getParameter().remove(ind);
if (includedVS.hasUrl()) {
childExpParams.addParameter(Parameters.newStringPart(
fhirContext, TerminologyServerClient.urlParamName, includedVS.getUrl()));
}
}
var versionParam = childExpParams.getParameter(TerminologyServerClient.versionParamName);
if (versionParam != null) {
var ind = childExpParams.getParameter().indexOf(versionParam);
childExpParams.getParameter().remove(ind);
if (includedVS.hasVersion()) {
childExpParams.addParameter(Parameters.newStringPart(
fhirContext,
TerminologyServerClient.versionParamName,
includedVS.getVersion()));
}
}
expandValueSet(
includedVS, childExpParams, terminologyEndpoint, valueSets, expandedList, repository);
}
getCodesInExpansion(fhirContext, includedVS.get()).forEach(code -> {
// Add the code if not already present
var existingCodes = getCodesInExpansion(fhirContext, expansion);
if (existingCodes == null
|| existingCodes.stream()
.noneMatch(expandedCode -> code.getSystem()
.equals(expandedCode.getSystem())
&& code.getCode().equals(expandedCode.getCode())
&& (StringUtils.isEmpty(code.getVersion())
|| code.getVersion().equals(expandedCode.getVersion())))) {
try {
addCodeToExpansion(fhirContext, expansion, code);
} catch (Exception ex) {
throw new UnprocessableEntityException(String.format(
"Encountered exception attempting to expand ValueSet %s: %s",
includedVS.get().getId(), ex.getMessage()));
}
}
});
// If any included expansion is naive it makes the expansion naive
if (includedVS.hasNaiveParameter() && !valueSet.hasNaiveParameter()) {
addParameterToExpansion(fhirContext, expansion, valueSet.createNaiveParameter());
}
});
// If any included expansion is naive it makes the expansion naive
if (vs.hasNaiveParameter() && !valueSet.hasNaiveParameter()) {
addParameterToExpansion(fhirContext, expansion, valueSet.createNaiveParameter());
} else {
throw new UnprocessableEntityException("Terminology Server expansion failed for ValueSet '"
+ valueSet.getUrl() + "' because Child ValueSet '" + reference + "' could not be found. ");
}
});
valueSet.setExpansion(expansion);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@
import org.hl7.fhir.instance.model.api.IBaseEnumFactory;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IDomainResource;
import org.opencds.cqf.fhir.utility.Canonicals;
import org.opencds.cqf.fhir.utility.Constants;
import org.opencds.cqf.fhir.utility.Parameters;
import org.opencds.cqf.fhir.utility.adapter.EndpointAdapter;
import org.opencds.cqf.fhir.utility.adapter.KnowledgeArtifactAdapter;
import org.opencds.cqf.fhir.utility.adapter.ParametersAdapter;
import org.opencds.cqf.fhir.utility.adapter.ValueSetAdapter;
import org.opencds.cqf.fhir.utility.search.Searches;

/**
* This class currently serves as a VSAC Terminology Server client as it expects the Endpoint provided to contain a VSAC username and api key.
Expand Down Expand Up @@ -93,6 +96,16 @@ public IBaseResource expand(
.execute();
}

public java.util.Optional<IDomainResource> getResource(
EndpointAdapter endpoint, String url, FhirVersionEnum versionEnum) {
return KnowledgeArtifactAdapter.findLatestVersion(
ctx.newRestfulGenericClient(getAddressBase(endpoint.getAddress()))
.search()
.forResource(getValueSetClass(versionEnum))
.where(Searches.byCanonical(url))
.execute());
}

private Class<? extends IBaseResource> getValueSetClass(FhirVersionEnum fhirVersion) {
switch (fhirVersion) {
case DSTU3:
Expand All @@ -106,9 +119,12 @@ private Class<? extends IBaseResource> getValueSetClass(FhirVersionEnum fhirVers
}
}

private String getAddressBase(String address) {
return getAddressBase(address, this.ctx);
}
// Strips resource and id from the endpoint address URL, these are not needed as the client constructs the URL.
// Converts http URLs to https
public String getAddressBase(String address) {
public static String getAddressBase(String address, FhirContext ctx) {
Objects.requireNonNull(address, "address must not be null");
if (address.startsWith("http://")) {
address = address.replaceFirst("http://", "https://");
Expand All @@ -120,7 +136,7 @@ public String getAddressBase(String address) {
// check if URL is in the format [base URL]/[resource type]/[id]
var maybeFhirType = Canonicals.getResourceType(address);
if (maybeFhirType != null && StringUtils.isNotBlank(maybeFhirType)) {
IBaseEnumFactory<?> factory = getEnumFactory();
IBaseEnumFactory<?> factory = TerminologyServerClient.getEnumFactory(ctx);
try {
factory.fromCode(maybeFhirType);
} catch (IllegalArgumentException e) {
Expand All @@ -142,8 +158,8 @@ public String getAddressBase(String address) {
return address;
}

private IBaseEnumFactory<?> getEnumFactory() {
switch (this.ctx.getVersion().getVersion()) {
public static IBaseEnumFactory<?> getEnumFactory(FhirContext ctx) {
switch (ctx.getVersion().getVersion()) {
case DSTU3:
return new org.hl7.fhir.dstu3.model.Enumerations.ResourceTypeEnumFactory();

Expand All @@ -155,7 +171,7 @@ private IBaseEnumFactory<?> getEnumFactory() {

default:
throw new UnprocessableEntityException("unsupported FHIR version: "
+ this.ctx.getVersion().getVersion().toString());
+ ctx.getVersion().getVersion().toString());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ protected void handleValueSets(
params,
terminologyEndpoint.map(e -> (EndpointAdapter) createAdapterForResource(e)),
valueSets,
expandedList);
expandedList,
repository);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import org.opencds.cqf.fhir.utility.adapter.IDependencyInfo;
import org.opencds.cqf.fhir.utility.adapter.KnowledgeArtifactAdapter;
import org.opencds.cqf.fhir.utility.adapter.LibraryAdapter;
import org.opencds.cqf.fhir.utility.search.Searches;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -206,7 +205,7 @@ private List<IDomainResource> internalRelease(
var alreadyUpdated = checkIfReferenceInList(preReleaseReference, resourcesToUpdate);
if (KnowledgeArtifactAdapter.checkIfRelatedArtifactIsOwned(component) && !alreadyUpdated.isPresent()) {
// get the latest version regardless of status because it's owned and we're releasing it
var latest = tryGetLatestVersion(preReleaseReference, repository);
var latest = VisitorHelper.tryGetLatestVersion(preReleaseReference, repository);
if (latest.isPresent()) {
checkNonExperimental(latest.get().get(), experimentalBehavior, repository);
// release components recursively
Expand All @@ -226,7 +225,7 @@ private List<IDomainResource> internalRelease(
}
} else if (!alreadyUpdated.isPresent()) {
// if it's a not-owned component just try to get the latest active version
tryGetLatestVersionWithStatus(preReleaseReference, repository, "active")
VisitorHelper.tryGetLatestVersionWithStatus(preReleaseReference, repository, "active")
.ifPresent(latestActive ->
// check if it's experimental
checkNonExperimental(latestActive.get(), experimentalBehavior, repository));
Expand Down Expand Up @@ -260,7 +259,7 @@ private void gatherDependencies(
"Owned resource reference not found during release: " + preReleaseReference);
}
} else {
res = tryGetLatestVersion(preReleaseReference, repository);
res = VisitorHelper.tryGetLatestVersion(preReleaseReference, repository);
}
if (res.isPresent()) {
// add to cache if resolvable
Expand Down Expand Up @@ -334,7 +333,8 @@ private void gatherDependencies(
// if not available in expansion parameters then try to find the latest version and update the
// dependency
if (StringUtils.isBlank(Canonicals.getVersion(dependency.getReference()))) {
maybeAdapter = tryGetLatestVersionWithStatus(dependency.getReference(), repository, "active")
maybeAdapter = VisitorHelper.tryGetLatestVersionWithStatus(
dependency.getReference(), repository, "active")
.map(adapter -> {
String versionedReference = addVersionToReference(dependency.getReference(), adapter);
dependency.setReference(versionedReference);
Expand Down Expand Up @@ -448,21 +448,6 @@ private KnowledgeArtifactAdapter getArtifactByCanonical(String inputReference, R
}
}

private Optional<KnowledgeArtifactAdapter> tryGetLatestVersionWithStatus(
String inputReference, Repository repository, String status) {
return KnowledgeArtifactAdapter.findLatestVersion(SearchHelper.searchRepositoryByCanonicalWithPagingWithParams(
repository, inputReference, Searches.byStatus(status)))
.map(res -> AdapterFactory.forFhirVersion(res.getStructureFhirVersionEnum())
.createKnowledgeArtifactAdapter(res));
}

private Optional<KnowledgeArtifactAdapter> tryGetLatestVersion(String inputReference, Repository repository) {
return KnowledgeArtifactAdapter.findLatestVersion(
SearchHelper.searchRepositoryByCanonicalWithPaging(repository, inputReference))
.map(res -> AdapterFactory.forFhirVersion(res.getStructureFhirVersionEnum())
.createKnowledgeArtifactAdapter(res));
}

private String addVersionToReference(String inputReference, KnowledgeArtifactAdapter adapter) {
if (adapter != null) {
String versionedReference = adapter.hasVersion()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@
import org.hl7.fhir.instance.model.api.ICompositeType;
import org.hl7.fhir.instance.model.api.IDomainResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.opencds.cqf.fhir.api.Repository;
import org.opencds.cqf.fhir.utility.BundleHelper;
import org.opencds.cqf.fhir.utility.Canonicals;
import org.opencds.cqf.fhir.utility.SearchHelper;
import org.opencds.cqf.fhir.utility.adapter.AdapterFactory;
import org.opencds.cqf.fhir.utility.adapter.KnowledgeArtifactAdapter;
import org.opencds.cqf.fhir.utility.search.Searches;

public class VisitorHelper {

Expand Down Expand Up @@ -144,4 +147,19 @@ private static Optional<String> findVersionInListMatchingResource(
public static boolean typeHasCoding(ICompositeType type, String system, String code) {
return false;
}

public static Optional<KnowledgeArtifactAdapter> tryGetLatestVersion(String inputReference, Repository repository) {
return KnowledgeArtifactAdapter.findLatestVersion(
SearchHelper.searchRepositoryByCanonicalWithPaging(repository, inputReference))
.map(res -> AdapterFactory.forFhirVersion(res.getStructureFhirVersionEnum())
.createKnowledgeArtifactAdapter(res));
}

public static Optional<KnowledgeArtifactAdapter> tryGetLatestVersionWithStatus(
String inputReference, Repository repository, String status) {
return KnowledgeArtifactAdapter.findLatestVersion(SearchHelper.searchRepositoryByCanonicalWithPagingWithParams(
repository, inputReference, Searches.byStatus(status)))
.map(res -> AdapterFactory.forFhirVersion(res.getStructureFhirVersionEnum())
.createKnowledgeArtifactAdapter(res));
}
}
Loading

0 comments on commit 23a78d7

Please sign in to comment.