diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/visitor/ReleaseVisitor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/visitor/ReleaseVisitor.java index 3ed4411df..3e47962d0 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/visitor/ReleaseVisitor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/visitor/ReleaseVisitor.java @@ -1,5 +1,7 @@ package org.opencds.cqf.fhir.cr.visitor; +import static org.opencds.cqf.fhir.utility.adapter.IAdapterFactory.createAdapterForResource; + import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException; @@ -31,8 +33,10 @@ import org.opencds.cqf.fhir.utility.SearchHelper; import org.opencds.cqf.fhir.utility.adapter.IAdapterFactory; import org.opencds.cqf.fhir.utility.adapter.IDependencyInfo; +import org.opencds.cqf.fhir.utility.adapter.IEndpointAdapter; import org.opencds.cqf.fhir.utility.adapter.IKnowledgeArtifactAdapter; import org.opencds.cqf.fhir.utility.adapter.ILibraryAdapter; +import org.opencds.cqf.fhir.utility.client.TerminologyServerClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,19 +45,23 @@ public class ReleaseVisitor extends BaseKnowledgeArtifactVisitor { private static final String ACTIVE = "active"; private Logger logger = LoggerFactory.getLogger(ReleaseVisitor.class); private static final String DEPENDSON = "depends-on"; + protected final TerminologyServerClient terminologyServerClient; public ReleaseVisitor(Repository repository) { super(repository); + terminologyServerClient = new TerminologyServerClient(fhirContext()); } @SuppressWarnings("unchecked") @Override public IBase visit(IKnowledgeArtifactAdapter rootAdapter, IBaseParameters operationParameters) { - Optional latestFromTxServer = - VisitorHelper.getBooleanParameter("latestFromTxServer", operationParameters); - // This check is to avoid partial releases and should be removed once the argument is supported. - if (latestFromTxServer.isPresent()) { - throw new NotImplementedOperationException("Support for 'latestFromTxServer' is not yet implemented."); + var latestFromTxServer = VisitorHelper.getBooleanParameter("latestFromTxServer", operationParameters) + .orElse(false); + Optional terminologyEndpoint = VisitorHelper.getResourceParameter( + "terminologyEndpoint", operationParameters) + .map(r -> (IEndpointAdapter) createAdapterForResource(r)); + if (latestFromTxServer && !terminologyEndpoint.isPresent()) { + throw new UnprocessableEntityException("latestFromTxServer = true but no terminologyEndpoint is available"); } var version = VisitorHelper.getStringParameter("version", operationParameters) .orElseThrow(() -> new UnprocessableEntityException("Version must be present")); @@ -81,10 +89,11 @@ public IBase visit(IKnowledgeArtifactAdapter rootAdapter, IBaseParameters operat rootAdapter, releaseVersion, rootEffectivePeriod, - latestFromTxServer.orElse(false), + latestFromTxServer, requireNonExperimental, new Date(), - new HashSet<>()); + new HashSet<>(), + terminologyEndpoint.orElse(null)); var rootArtifactOriginalDependencies = new ArrayList(rootAdapter.getDependencies()); // Get list of extensions which need to be preserved var originalDependenciesWithExtensions = rootArtifactOriginalDependencies.stream() @@ -114,7 +123,9 @@ public IBase visit(IKnowledgeArtifactAdapter rootAdapter, IBaseParameters operat releasedResources, new HashMap<>(), systemVersionParams, - canonicalVersionParams); + canonicalVersionParams, + latestFromTxServer, + terminologyEndpoint.orElse(null)); if (rootAdapter.get().fhirType().equals("Library")) { ((ILibraryAdapter) rootAdapter).setExpansionParameters(systemVersionParams, canonicalVersionParams); } @@ -184,7 +195,8 @@ private List internalRelease( boolean latestFromTxServer, Optional experimentalBehavior, Date current, - Set releasedResources) + Set releasedResources, + IEndpointAdapter endpoint) throws NotImplementedOperationException, ResourceNotFoundException { var resourcesToUpdate = new ArrayList(); if (releasedResources.contains(artifactAdapter.getCanonical())) { @@ -205,32 +217,52 @@ private List internalRelease( // If a version IS specified then `tryGetLatestVersion` // will return that version. var alreadyUpdated = checkIfReferenceInList(preReleaseReference, resourcesToUpdate); - if (IKnowledgeArtifactAdapter.checkIfRelatedArtifactIsOwned(component) && !alreadyUpdated.isPresent()) { - // get the latest version regardless of status because it's owned and we're releasing it - var latest = VisitorHelper.tryGetLatestVersion(preReleaseReference, repository); - if (latest.isPresent()) { - checkNonExperimental(latest.get().get(), experimentalBehavior, repository); - // release components recursively - resourcesToUpdate.addAll(internalRelease( - latest.get(), - version, - rootEffectivePeriod, - latestFromTxServer, - experimentalBehavior, - current, - releasedResources)); + if (!alreadyUpdated.isPresent()) { + var resourceType = Canonicals.getResourceType(preReleaseReference); + var prereleaseReferenceVersion = Canonicals.getVersion(preReleaseReference); + if (resourceType != null + && resourceType.equals("ValueSet") + && prereleaseReferenceVersion == null + && latestFromTxServer) { + var latest = + terminologyServerClient.getResource(endpoint, preReleaseReference, this.fhirVersion()); + if (latest.isPresent()) { + checkNonExperimental(latest.get(), experimentalBehavior, repository); + // release components recursively + resourcesToUpdate.addAll(internalRelease( + IAdapterFactory.forFhirVersion(fhirVersion()) + .createKnowledgeArtifactAdapter(latest.get()), + version, + rootEffectivePeriod, + latestFromTxServer, + experimentalBehavior, + current, + releasedResources, + endpoint)); + } + } else if (IKnowledgeArtifactAdapter.checkIfRelatedArtifactIsOwned(component)) { + // get the latest version regardless of status because it's owned and we're releasing it + var latest = VisitorHelper.tryGetLatestVersion(preReleaseReference, repository); + if (latest.isPresent()) { + checkNonExperimental(latest.get().get(), experimentalBehavior, repository); + // release components recursively + resourcesToUpdate.addAll(internalRelease( + latest.get(), + version, + rootEffectivePeriod, + latestFromTxServer, + experimentalBehavior, + current, + releasedResources, + endpoint)); + } } else { - // if missing throw because it's an owned resource - throw new ResourceNotFoundException(String.format( - "Resource with URL '%s' is Owned by this repository and referenced by resource '%s', but no active version was found on the server.", - preReleaseReference, artifactAdapter.getUrl())); + // if it's a not-owned component just try to get the latest active version + VisitorHelper.tryGetLatestVersionWithStatus(preReleaseReference, repository, ACTIVE) + .ifPresent(latestActive -> + // check if it's experimental + checkNonExperimental(latestActive.get(), experimentalBehavior, repository)); } - } else if (!alreadyUpdated.isPresent()) { - // if it's a not-owned component just try to get the latest active version - VisitorHelper.tryGetLatestVersionWithStatus(preReleaseReference, repository, ACTIVE) - .ifPresent(latestActive -> - // check if it's experimental - checkNonExperimental(latestActive.get(), experimentalBehavior, repository)); } } } @@ -245,7 +277,9 @@ private void gatherDependencies( List releasedResources, Map alreadyUpdatedDependencies, List systemVersionExpansionParameters, - List canonicalVersionExpansionParameters) { + List canonicalVersionExpansionParameters, + Boolean latestFromTxServer, + IEndpointAdapter endpoint) { if (artifactAdapter == null) { return; } @@ -317,23 +351,23 @@ private void gatherDependencies( releasedResources, alreadyUpdatedDependencies, systemVersionExpansionParameters, - canonicalVersionExpansionParameters); + canonicalVersionExpansionParameters, + latestFromTxServer, + endpoint); } else { // try to get versions from expansion parameters if they are available - var resourceType = Canonicals.getResourceType(dependency.getReference()) == null - ? null - : SearchHelper.getResourceType(repository, dependency); + String resourceType = getResourceType(dependency); if (StringUtils.isBlank(Canonicals.getVersion(dependency.getReference()))) { // This needs to be updated once we support requireVersionedDependencies Optional expansionParametersVersion = Optional.empty(); // assume if we can't figure out the resource type it's a CodeSystem - if (resourceType == null || resourceType.getSimpleName().equals("CodeSystem")) { + if (resourceType == null || resourceType.equals("CodeSystem")) { expansionParametersVersion = systemVersionExpansionParameters.stream() .filter(canonical -> !StringUtils.isBlank(Canonicals.getUrl(canonical))) .filter(canonical -> Canonicals.getUrl(canonical).equals(dependency.getReference())) .findAny(); - } else if (resourceType.getSimpleName().equals("ValueSet")) { + } else if (resourceType.equals("ValueSet")) { expansionParametersVersion = canonicalVersionExpansionParameters.stream() .filter(canonical -> Canonicals.getUrl(canonical).equals(dependency.getReference())) @@ -350,15 +384,19 @@ private void gatherDependencies( // dependency if (StringUtils.isBlank(Canonicals.getVersion(dependency.getReference()))) { final var url = dependencyUrl; - maybeAdapter = VisitorHelper.tryGetLatestVersionWithStatus( - dependency.getReference(), repository, ACTIVE) - .map(adapter -> { - String versionedReference = - addVersionToReference(dependency.getReference(), adapter); - dependency.setReference(versionedReference); - alreadyUpdatedDependencies.put(url, adapter.get()); - return adapter; - }); + if (resourceType != null && resourceType.equals("ValueSet") && latestFromTxServer) { + maybeAdapter = terminologyServerClient + .getResource(endpoint, dependency.getReference(), this.fhirVersion()) + .map(r -> (IKnowledgeArtifactAdapter) createAdapterForResource(r)); + } else { + maybeAdapter = VisitorHelper.tryGetLatestVersionWithStatus( + dependency.getReference(), repository, ACTIVE); + } + maybeAdapter.ifPresent(adapter -> { + String versionedReference = addVersionToReference(dependency.getReference(), adapter); + dependency.setReference(versionedReference); + alreadyUpdatedDependencies.put(url, adapter.get()); + }); } else { // This is a versioned reference, just get the dependency maybeAdapter = @@ -374,7 +412,9 @@ private void gatherDependencies( releasedResources, alreadyUpdatedDependencies, systemVersionExpansionParameters, - canonicalVersionExpansionParameters); + canonicalVersionExpansionParameters, + latestFromTxServer, + endpoint); } else { alreadyUpdatedDependencies.put(dependencyUrl, null); } @@ -394,6 +434,12 @@ private void gatherDependencies( } } + private String getResourceType(IDependencyInfo dependency) { + return Canonicals.getResourceType(dependency.getReference()) == null + ? null + : SearchHelper.getResourceType(repository, dependency).getSimpleName(); + } + private void checkNonExperimental( IDomainResource resource, Optional experimentalBehavior, Repository repository) throws UnprocessableEntityException { @@ -447,15 +493,14 @@ private IKnowledgeArtifactAdapter getArtifactByCanonical(String inputReference, List matchingResources = VisitorHelper.getMetadataResourcesFromBundle( SearchHelper.searchRepositoryByCanonicalWithPaging(repository, inputReference)) .stream() - .map(r -> IAdapterFactory.forFhirVersion(r.getStructureFhirVersionEnum()) - .createKnowledgeArtifactAdapter(r)) + .map(r -> (IKnowledgeArtifactAdapter) createAdapterForResource(r)) .collect(Collectors.toList()); if (matchingResources.isEmpty()) { return null; } else if (matchingResources.size() == 1) { return matchingResources.get(0); } else { - logger.info("Multiple resources found matching {}", inputReference); + logger.info("Multiple resources found matching {}, used the first one", inputReference); return matchingResources.get(0); } } diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/visitor/dstu3/ReleaseVisitorTests.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/visitor/dstu3/ReleaseVisitorTests.java index c3e8a1840..67b61ffff 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/visitor/dstu3/ReleaseVisitorTests.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/visitor/dstu3/ReleaseVisitorTests.java @@ -20,7 +20,6 @@ import java.util.List; import java.util.Optional; import java.util.stream.Collectors; -import org.hl7.fhir.dstu3.model.BooleanType; import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; import org.hl7.fhir.dstu3.model.CodeType; @@ -484,31 +483,6 @@ void releaseResource_propagate_effective_period() { repo); } - @Test - void releaseResource_latestFromTx_NotSupported_test() { - Bundle bundle = (Bundle) jsonParser.parseResource( - ReleaseVisitorTests.class.getResourceAsStream("Bundle-small-approved-draft.json")); - repo.transaction(bundle); - - String actualErrorMessage = ""; - - Parameters params = parameters( - part("version", "1.2.3"), - part("versionBehavior", new CodeType("default")), - part("latestFromTxServer", new BooleanType(true))); - var releaseVisitor = new ReleaseVisitor(repo); - Library library = repo.read(Library.class, new IdType("Library/SpecificationLibrary")) - .copy(); - ILibraryAdapter libraryAdapter = new AdapterFactory().createLibrary(library); - - try { - libraryAdapter.accept(releaseVisitor, params); - } catch (Exception e) { - actualErrorMessage = e.getMessage(); - } - assertTrue(actualErrorMessage.contains("not yet implemented")); - } - @Test void release_missing_approvalDate_validation_test() { Bundle bundle = (Bundle) jsonParser.parseResource( diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/visitor/r4/ReleaseVisitorTests.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/visitor/r4/ReleaseVisitorTests.java index de3c22e59..9cf5cd99a 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/visitor/r4/ReleaseVisitorTests.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/visitor/r4/ReleaseVisitorTests.java @@ -21,7 +21,6 @@ import java.util.Optional; import java.util.stream.Collectors; import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.r4.model.BooleanType; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.CanonicalType; @@ -459,31 +458,6 @@ void releaseResource_propagate_effective_period() { repo); } - @Test - void releaseResource_latestFromTx_NotSupported_test() { - Bundle bundle = (Bundle) jsonParser.parseResource( - ReleaseVisitorTests.class.getResourceAsStream("Bundle-small-approved-draft.json")); - repo.transaction(bundle); - - String actualErrorMessage = ""; - - Parameters params = parameters( - part("version", "1.2.3"), - part("versionBehavior", new CodeType("default")), - part("latestFromTxServer", new BooleanType(true))); - ReleaseVisitor releaseVisitor = new ReleaseVisitor(repo); - Library library = repo.read(Library.class, new IdType("Library/SpecificationLibrary")) - .copy(); - ILibraryAdapter libraryAdapter = new AdapterFactory().createLibrary(library); - - try { - libraryAdapter.accept(releaseVisitor, params); - } catch (Exception e) { - actualErrorMessage = e.getMessage(); - } - assertTrue(actualErrorMessage.contains("not yet implemented")); - } - @Test void release_missing_approvalDate_validation_test() { Bundle bundle = (Bundle) jsonParser.parseResource( diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/visitor/r5/ReleaseVisitorTests.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/visitor/r5/ReleaseVisitorTests.java index 1c827730e..b8a4b537d 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/visitor/r5/ReleaseVisitorTests.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/visitor/r5/ReleaseVisitorTests.java @@ -21,7 +21,6 @@ import java.util.Optional; import java.util.stream.Collectors; import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.r5.model.BooleanType; import org.hl7.fhir.r5.model.Bundle; import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r5.model.CanonicalType; @@ -456,31 +455,6 @@ void releaseResource_propagate_effective_period() { repo); } - @Test - void releaseResource_latestFromTx_NotSupported_test() { - Bundle bundle = (Bundle) jsonParser.parseResource( - ReleaseVisitorTests.class.getResourceAsStream("Bundle-small-approved-draft.json")); - repo.transaction(bundle); - - String actualErrorMessage = ""; - - Parameters params = parameters( - part("version", "1.2.3"), - part("versionBehavior", new CodeType("default")), - part("latestFromTxServer", new BooleanType(true))); - ReleaseVisitor releaseVisitor = new ReleaseVisitor(repo); - Library library = repo.read(Library.class, new IdType("Library/SpecificationLibrary")) - .copy(); - ILibraryAdapter libraryAdapter = new AdapterFactory().createLibrary(library); - - try { - libraryAdapter.accept(releaseVisitor, params); - } catch (Exception e) { - actualErrorMessage = e.getMessage(); - } - assertTrue(actualErrorMessage.contains("not yet implemented")); - } - @Test void release_missing_approvalDate_validation_test() { Bundle bundle = (Bundle) jsonParser.parseResource(