Skip to content

Commit

Permalink
Merge gatherdependencies into package updates (#502)
Browse files Browse the repository at this point in the history
* Remove non CRMI logic.  Update PackageProcessor to use visitor.

* Add adapters for Questionnaire and StructureDefinition

* Shortening some class names

* Add type and dataRequirement

* Refactor ValueSet expansion

* Extract expansion logic out of PackageVisitor

* Add tests

* Fix class name

* clean up adapters

* stop expecting getRelatedArtifacts to pass a mutable ref

* Enable test

* Add tests

* Add tests

* Update terminology client to use url param (#488)

* add method to client without valueset

* spotless

---------

Co-authored-by: taha.attari@smilecdr.com <taha.attari@smilecdr.com>

* Add tests

* Fix test

* shorter

* Add test

* cleanup

* cleanup

* #494: Added measure support for the release operation

* Questionnaire and StructureDefinition adapter work and DSTU3 extension support

* spotless

* add tests

* add support for cqf-resourceType extension

* fix Javadoc

* typo

* add MeasureAdapters

* refactor to completely separate component and dependency resolution
- moved component reference updates into internalRelease
- created a dedicated resource cache for dependency resolution so it wouldn't get mixed up with the released resources
- updated tests with newly found dependencies

* remove isEmpty()

* cleanup
- make sure everything which can get a descriptor gets one
- update var names and comments

* check the expansion parameters for dependency versions

* update expansion parameters with new values

* update tests

* spotless

* update Measure Test, check descriptors are set correctly, expansion parameters test pending

* remove unsupported

* check exp params, data requirements and resource type extensions

* spotless

* update tests to increase coverage and handle edge cases

* remove extension generic

* spotless

* looks like HAPI auto prepends # to contained IDs

* cleanup

* add hash to id

* more hash issues

* add tests

* cleanup and add tests

* add tests

* cleanup and tests

* cleanup

* add tests

* spotless

* add tests

* Add MeasureAdapter

* fix method signatures

* Add tests

* add tests

* Add dependency tracing logic to MeasureAdapters

* remove unnecessary methods

* update adapters

* add tests

* fix merge issues

* remove old constants

* update tests

* update tests

* spotless

* update tests and cqfm vs crmi edr

* spotless

* cleanup and tests

---------

Co-authored-by: Brenin Rhodes <brenin@alphora.com>
Co-authored-by: taha.attari@smilecdr.com <taha.attari@smilecdr.com>
Co-authored-by: Bryn Rhodes <bryn@databaseconsultinggroup.com>
  • Loading branch information
4 people authored Aug 16, 2024
1 parent 1f20345 commit 40fe754
Show file tree
Hide file tree
Showing 25 changed files with 17,177 additions and 194 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ public static <CanonicalType extends IPrimitiveType<String>> String getUrl(Canon
public static String getUrl(String canonical) {
checkNotNull(canonical);

if (!canonical.contains("/")) {
if (!canonical.contains("/") && !canonical.startsWith("urn:uuid") && !canonical.startsWith("urn:oid")) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ private Constants() {}
public static final String CPG_CUSTOM_ACTIVITY_KIND =
"http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-custom-activity-kind";
public static final String CPG_ACTIVITY_KIND = "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-activity-kind";
public static final String CQF_RESOURCETYPE = "http://hl7.org/fhir/StructureDefinition/cqf-resourceType";

// DSTU3 CQF Extensions
public static final String CQIF_LIBRARY = "http://hl7.org/fhir/StructureDefinition/cqif-library";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,21 @@

import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.opencds.cqf.fhir.api.Repository;
import org.opencds.cqf.fhir.utility.adapter.IDependencyInfo;
import org.opencds.cqf.fhir.utility.search.Searches;

public class SearchHelper {
Expand Down Expand Up @@ -72,21 +78,39 @@ public static <CanonicalType extends IPrimitiveType<String>> IBaseResource searc
* @param canonical the canonical url to search for
* @return
*/
private static <CanonicalType extends IPrimitiveType<String>> Class<? extends IBaseResource> getResourceType(
public static <CanonicalType extends IPrimitiveType<String>> Class<? extends IBaseResource> getResourceType(
Repository repository, CanonicalType canonical) {
Class<? extends IBaseResource> resourceType = null;
try {
var resourceTypeString = Canonicals.getResourceType(canonical);
if (StringUtils.isEmpty(resourceTypeString)) {
throw new DataFormatException();
}
resourceType = repository
.fhirContext()
.getResourceDefinition(Canonicals.getResourceType(canonical))
.getResourceDefinition(resourceTypeString)
.getImplementingClass();
} catch (DataFormatException e) {
// TODO: Use the "cqf-resourceType" extension to figure this out, if it's present
// NOTE: This is based on the assumption that only CodeSystems don't follow the canonical pattern...
resourceType =
repository.fhirContext().getResourceDefinition("CodeSystem").getImplementingClass();
// Use the "cqf-resourceType" extension to figure this out, if it's present
var cqfResourceTypeExt = getResourceTypeStringFromCqfResourceTypeExtension(canonical);
if (cqfResourceTypeExt.isPresent()) {
try {
resourceType = repository
.fhirContext()
.getResourceDefinition(cqfResourceTypeExt.get())
.getImplementingClass();
} catch (DataFormatException | NullPointerException e2) {
throw new UnprocessableEntityException(
"cqf-resourceType extension contains invalid resource type: " + cqfResourceTypeExt.get());
}
} else {
// NOTE: This is based on the assumption that only CodeSystems don't follow the canonical pattern...
resourceType = repository
.fhirContext()
.getResourceDefinition("CodeSystem")
.getImplementingClass();
}
}

return resourceType;
}

Expand All @@ -100,6 +124,85 @@ private static <CanonicalType extends IPrimitiveType<String>> Class<? extends IB
* If no extension is present, the type of the canonical is assumed to be CodeSystem, on
* the grounds that most (if not all) non-conventional URLs are for CodeSystem uris.
*
* @param <CanonicalType>
* @param repository the repository to search
* @param dependencyInfo the canonical url to search for
* @return
*/
public static Class<? extends IBaseResource> getResourceType(
Repository repository, IDependencyInfo dependencyInfo) {
Class<? extends IBaseResource> resourceType = null;
try {
var resourceTypeString = Canonicals.getResourceType(dependencyInfo.getReference());
if (StringUtils.isEmpty(resourceTypeString)) {
throw new DataFormatException();
}
resourceType = repository
.fhirContext()
.getResourceDefinition(resourceTypeString)
.getImplementingClass();
} catch (DataFormatException e) {
// Use the "cqf-resourceType" extension to figure this out, if it's present
var cqfResourceTypeExt = getResourceTypeStringFromCqfResourceTypeExtension(dependencyInfo.getExtension());
if (cqfResourceTypeExt.isPresent()) {
try {
resourceType = repository
.fhirContext()
.getResourceDefinition(cqfResourceTypeExt.get())
.getImplementingClass();
} catch (DataFormatException | NullPointerException e2) {
throw new UnprocessableEntityException(
"cqf-resourceType extension contains invalid resource type: " + cqfResourceTypeExt.get());
}
} else {
// NOTE: This is based on the assumption that only CodeSystems don't follow the canonical pattern...
resourceType = repository
.fhirContext()
.getResourceDefinition("CodeSystem")
.getImplementingClass();
}
}
return resourceType;
}

private static <CanonicalType extends IPrimitiveType<String>>
Optional<String> getResourceTypeStringFromCqfResourceTypeExtension(CanonicalType canonical) {
return getResourceTypeStringFromCqfResourceTypeExtension(getExtensions(canonical));
}

@SuppressWarnings({"unchecked", "rawtypes"})
private static Optional<String> getResourceTypeStringFromCqfResourceTypeExtension(
List<? extends IBaseExtension> extensions) {
return extensions.stream()
.filter(ext -> ext.getUrl().contains("cqf-resourceType"))
.findAny()
.map(ext -> ((IPrimitiveType<String>) ext.getValue()).getValue());
}

@SuppressWarnings({"unchecked", "rawtypes"})
private static <CanonicalType extends IPrimitiveType<String>> List<IBaseExtension> getExtensions(
CanonicalType canonical) {
if (canonical instanceof org.hl7.fhir.dstu3.model.PrimitiveType) {
return ((org.hl7.fhir.dstu3.model.PrimitiveType<String>) canonical)
.getExtension().stream().map(ext -> (IBaseExtension) ext).collect(Collectors.toList());
} else if (canonical instanceof org.hl7.fhir.r4.model.PrimitiveType) {
return ((org.hl7.fhir.r4.model.PrimitiveType<String>) canonical)
.getExtension().stream().map(ext -> (IBaseExtension) ext).collect(Collectors.toList());
} else if (canonical instanceof org.hl7.fhir.r5.model.PrimitiveType) {
return ((org.hl7.fhir.r5.model.PrimitiveType<String>) canonical)
.getExtension().stream().map(ext -> (IBaseExtension) ext).collect(Collectors.toList());
} else {
throw new UnprocessableEntityException("Unsupported FHIR version for canonical: " + canonical.getValue());
}
}

/**
* Gets the resource type for the given canonical, based on the convention that canonical
* URLs are of the form [base]/[resourceType]/[tail]
*
* If the URL does not conform to the convention, the type of the canonical is assumed to be CodeSystem, on
* the grounds that most (if not all) non-conventional URLs are for CodeSystem uris.
*
* @param repository
* @param canonical
* @return
Expand All @@ -112,7 +215,7 @@ private static Class<? extends IBaseResource> getResourceType(Repository reposit
.getResourceDefinition(Canonicals.getResourceType(canonical))
.getImplementingClass();
} catch (RuntimeException e) {
// TODO: Use the "cqf-resourceType" extension to figure this out, if it's present
// Can't use the "cqf-resourceType" extension to figure this out because we just get a canonical string
// NOTE: This is based on the assumption that only CodeSystems don't follow the canonical pattern...
resourceType =
repository.fhirContext().getResourceDefinition("CodeSystem").getImplementingClass();
Expand Down Expand Up @@ -177,6 +280,21 @@ public static <CanonicalType extends IPrimitiveType<String>> IBaseBundle searchR
return searchRepositoryByCanonicalWithPaging(repository, canonical, resourceType);
}

/**
* Searches the given Repository and handles paging to return all entries
*
* @param additionalSearchParams search parameters to pass on to the repository
* @param repository the repository to search
* @param canonical the canonical url to search for
* @return
*/
public static IBaseBundle searchRepositoryByCanonicalWithPagingWithParams(
Repository repository, String canonical, Map<String, List<IQueryParameterType>> additionalSearchParams) {
var resourceType = getResourceType(repository, canonical);
return searchRepositoryByCanonicalWithPagingWithParams(
repository, canonical, resourceType, additionalSearchParams);
}

/**
* Searches the given Repository and handles paging to return all entries
*
Expand All @@ -190,9 +308,32 @@ public static <CanonicalType extends IPrimitiveType<String>> IBaseBundle searchR
public static <CanonicalType extends IPrimitiveType<String>, R extends IBaseResource>
IBaseBundle searchRepositoryByCanonicalWithPaging(
Repository repository, CanonicalType canonical, Class<R> resourceType) {
return searchRepositoryByCanonicalWithPagingWithParams(repository, canonical, resourceType, null);
}

/**
* Searches the given Repository and handles paging to return all entries
*
* @param <CanonicalType> an IPrimitiveType<String> type
* @param <R> an IBaseResource type
* @param repository the repository to search
* @param canonical the canonical url to search for
* @param resourceType the class of the IBaseResource type
* @param additionalSearchParams extra search parameters to search with
* @return
*/
public static <CanonicalType extends IPrimitiveType<String>, R extends IBaseResource>
IBaseBundle searchRepositoryByCanonicalWithPagingWithParams(
Repository repository,
CanonicalType canonical,
Class<R> resourceType,
Map<String, List<IQueryParameterType>> additionalSearchParams) {
var url = Canonicals.getUrl(canonical);
var version = Canonicals.getVersion(canonical);
var searchParams = version == null ? Searches.byUrl(url) : Searches.byUrlAndVersion(url, version);
if (additionalSearchParams != null) {
searchParams.putAll(additionalSearchParams);
}
var searchResult = searchRepositoryWithPaging(repository, resourceType, searchParams, Collections.emptyMap());

return searchResult;
Expand All @@ -209,9 +350,30 @@ IBaseBundle searchRepositoryByCanonicalWithPaging(
*/
public static <R extends IBaseResource> IBaseBundle searchRepositoryByCanonicalWithPaging(
Repository repository, String canonical, Class<R> resourceType) {
return searchRepositoryByCanonicalWithPagingWithParams(repository, canonical, resourceType, null);
}

/**
* Searches the given Repository and handles paging to return all entries
*
* @param <R> an IBaseResource type
* @param repository the repository to search
* @param canonical the canonical url to search for
* @param resourceType the class of the IBaseResource type
* @param additionalSearchParams extra search parameters to search with
* @return
*/
public static <R extends IBaseResource> IBaseBundle searchRepositoryByCanonicalWithPagingWithParams(
Repository repository,
String canonical,
Class<R> resourceType,
Map<String, List<IQueryParameterType>> additionalSearchParams) {
var url = Canonicals.getUrl(canonical);
var version = Canonicals.getVersion(canonical);
var searchParams = version == null ? Searches.byUrl(url) : Searches.byUrlAndVersion(url, version);
if (additionalSearchParams != null) {
searchParams.putAll(additionalSearchParams);
}
var searchResult = searchRepositoryWithPaging(repository, resourceType, searchParams, Collections.emptyMap());

return searchResult;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ static boolean isSupportedMetadataResource(IBaseResource resource) {
|| resource instanceof org.hl7.fhir.r5.model.MetadataResource;
}

// TODO: Make this a semver sort
static Optional<IDomainResource> findLatestVersion(IBaseBundle bundle) {
var sorted = BundleHelper.getEntryResources(bundle).stream()
.filter(r -> isSupportedMetadataResource(r))
Expand All @@ -324,6 +325,10 @@ static Optional<IDomainResource> findLatestVersion(IBaseBundle bundle) {
}
}

default Optional<IBaseParameters> getExpansionParameters() {
return Optional.empty();
}

String releaseLabelUrl = "http://hl7.org/fhir/StructureDefinition/artifact-releaseLabel";
String releaseDescriptionUrl = "http://hl7.org/fhir/StructureDefinition/artifact-releaseDescription";
String usPhContextTypeUrl = "http://hl7.org/fhir/us/ecr/CodeSystem/us-ph-usage-context-type";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,7 @@ public interface LibraryAdapter extends KnowledgeArtifactAdapter {
<T extends ICompositeType> LibraryAdapter setDataRequirement(List<T> dataRequirement);

List<? extends ICompositeType> getUseContext();

void setExpansionParameters(
List<String> systemVersionExpansionParameters, List<String> canonicalVersionExpansionParameters);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,23 @@
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.hl7.fhir.dstu3.model.Attachment;
import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.DataRequirement;
import org.hl7.fhir.dstu3.model.Library;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent;
import org.hl7.fhir.dstu3.model.Reference;
import org.hl7.fhir.dstu3.model.RelatedArtifact;
import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.dstu3.model.UsageContext;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.ICompositeType;
import org.hl7.fhir.instance.model.api.IDomainResource;
import org.opencds.cqf.fhir.utility.Constants;
import org.opencds.cqf.fhir.utility.adapter.DependencyInfo;
import org.opencds.cqf.fhir.utility.adapter.IDependencyInfo;

Expand Down Expand Up @@ -141,4 +148,58 @@ public <T extends ICompositeType> LibraryAdapter setDataRequirement(List<T> data
public List<UsageContext> getUseContext() {
return getLibrary().getUseContext();
}

@Override
public Optional<IBaseParameters> getExpansionParameters() {
return getLibrary().getExtension().stream()
.filter(ext -> ext.getUrl().equals(Constants.CQF_EXPANSION_PARAMETERS))
.findAny()
.map(ext -> ((Reference) ext.getValue()).getReference())
.map(ref -> {
if (getLibrary().hasContained()) {
return getLibrary().getContained().stream()
.filter(containedResource ->
containedResource.getId().equals(ref))
.findFirst()
.map(r -> (IBaseParameters) r)
.orElse(null);
}
return null;
});
}

@Override
public void setExpansionParameters(
List<String> systemVersionExpansionParameters, List<String> canonicalVersionExpansionParameters) {
var newParameters = new ArrayList<ParametersParameterComponent>();
if (systemVersionExpansionParameters != null && !systemVersionExpansionParameters.isEmpty()) {
for (String parameter : systemVersionExpansionParameters) {
var param = new ParametersParameterComponent();
param.setName("system-version");
param.setValue(new UriType(parameter));
newParameters.add(param);
}
}
if (canonicalVersionExpansionParameters != null && !canonicalVersionExpansionParameters.isEmpty()) {
for (String parameter : canonicalVersionExpansionParameters) {
var param = new ParametersParameterComponent();
param.setName("canonical-version");
param.setValue(new UriType(parameter));
newParameters.add(param);
}
}
var existingExpansionParameters = getExpansionParameters();
if (existingExpansionParameters.isPresent()) {
((Parameters) existingExpansionParameters.get()).setParameter(newParameters);
} else {
var id = "#exp-params";
var newExpansionParameters = new Parameters();
newExpansionParameters.setParameter(newParameters);
newExpansionParameters.setId(id);
getLibrary().addContained(newExpansionParameters);
var expansionParamsExt = getLibrary().addExtension();
expansionParamsExt.setUrl(Constants.CQF_EXPANSION_PARAMETERS);
expansionParamsExt.setValue(new Reference(id));
}
}
}
Loading

0 comments on commit 40fe754

Please sign in to comment.