Skip to content

Commit

Permalink
APHL-1023-withdraw_draft_programs (#511)
Browse files Browse the repository at this point in the history
* initial withdraw implementation

* add test for non-draft library + only depends-on cascade delete

* first check request resource id to work with

* use specific withdraw resource file

* return bundle and delete on ecr

* fix in memory fhir repo

* run code formatting

* add InMemoryRepository tests

* reformat code

* add getEntryRequestId & isEntryRequestDelete tests

* run code check

* throw error if delete request entry is not present

* artifacts that is owned and has composed-of relationship

* run code check

* update Exception thrown in in memory repo

* run code check

* add unsupported fhir version tests to bundle helper

* rename getComponents method name

---------

Co-authored-by: Ivan Baisi <[email protected]>
  • Loading branch information
ibaisi and ibaisismile authored Sep 17, 2024
1 parent 8dd94d5 commit 068cc68
Show file tree
Hide file tree
Showing 9 changed files with 12,785 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.instance.model.api.IBaseBackboneElement;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle.BundleEntryRequestComponent;

public class BundleHelper {
private BundleHelper() {}
Expand Down Expand Up @@ -185,6 +189,37 @@ public static boolean isEntryRequestPost(FhirVersionEnum fhirVersion, IBaseBackb
}
}

/**
* Checks if an entry has a request type of DELETE
*
* @param fhirVersion FhirVersionEnum
* @param entry IBaseBackboneElement type
* @return
*/
public static boolean isEntryRequestDelete(FhirVersionEnum fhirVersion, IBaseBackboneElement entry) {
switch (fhirVersion) {
case DSTU3:
return Optional.ofNullable(((org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent) entry).getRequest())
.map(Bundle.BundleEntryRequestComponent::getMethod)
.filter(r -> r == org.hl7.fhir.dstu3.model.Bundle.HTTPVerb.DELETE)
.isPresent();
case R4:
return Optional.ofNullable(((org.hl7.fhir.r4.model.Bundle.BundleEntryComponent) entry).getRequest())
.map(BundleEntryRequestComponent::getMethod)
.filter(r -> r == org.hl7.fhir.r4.model.Bundle.HTTPVerb.DELETE)
.isPresent();
case R5:
return Optional.ofNullable(((org.hl7.fhir.r5.model.Bundle.BundleEntryComponent) entry).getRequest())
.map(org.hl7.fhir.r5.model.Bundle.BundleEntryRequestComponent::getMethod)
.filter(r -> r == org.hl7.fhir.r5.model.Bundle.HTTPVerb.DELETE)
.isPresent();

default:
throw new IllegalArgumentException(
String.format("Unsupported version of FHIR: %s", fhirVersion.getFhirVersionString()));
}
}

/**
* Returns the list of entries from the Bundle
*
Expand All @@ -207,6 +242,40 @@ public static List<? extends IBaseBackboneElement> getEntry(IBaseBundle bundle)
}
}

/**
* Gets request id if present
*
* @param fhirVersion FhirVersionEnum
* @param entry IBaseBackboneElement type
* @return
*/
public static Optional<IIdType> getEntryRequestId(FhirVersionEnum fhirVersion, IBaseBackboneElement entry) {
switch (fhirVersion) {
case DSTU3:
return Optional.ofNullable(((org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent) entry)
.getRequest()
.getUrl())
.map(Canonicals::getIdPart)
.map(IdType::new);
case R4:
return Optional.ofNullable(((org.hl7.fhir.r4.model.Bundle.BundleEntryComponent) entry)
.getRequest()
.getUrl())
.map(Canonicals::getIdPart)
.map(org.hl7.fhir.r4.model.IdType::new);
case R5:
return Optional.ofNullable(((org.hl7.fhir.r5.model.Bundle.BundleEntryComponent) entry)
.getRequest()
.getUrl())
.map(Canonicals::getIdPart)
.map(org.hl7.fhir.r5.model.IdType::new);

default:
throw new IllegalArgumentException(
String.format("Unsupported version of FHIR: %s", fhirVersion.getFhirVersionString()));
}
}

/**
* Sets the list of entries of the Bundle
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,15 @@ public static IBaseBackboneElement createEntry(IBaseResource resource, boolean i
}
return entry;
}

public static IBaseBackboneElement deleteEntry(IBaseResource resource) {
final var fhirVersion = resource.getStructureFhirVersionEnum();
final var entry = BundleHelper.newEntryWithResource(resource.getStructureFhirVersionEnum(), resource);
var requestUrl = resource.fhirType() + "/" + resource.getIdElement().getIdPart();

final var request = BundleHelper.newRequest(fhirVersion, "DELETE", requestUrl);
BundleHelper.setEntryRequest(fhirVersion, entry, request);

return entry;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,15 @@ protected static Class<IBaseBundle> getBundleClass(Repository repository) {
repository.fhirContext().getResourceDefinition("Bundle").getImplementingClass();
}

/**
* Gets a resource class
*
* @param repository the repository to search
* @param resourceType String of the resource typeget
* @return
*/
@SuppressWarnings("unchecked")
protected static Class<IBaseResource> getResourceClass(Repository repository, String resourceType) {
public static Class<IBaseResource> getResourceClass(Repository repository, String resourceType) {
return (Class<IBaseResource>)
repository.fhirContext().getResourceDefinition(resourceType).getImplementingClass();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
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.Ids;
import org.opencds.cqf.fhir.utility.SearchHelper;

public class InMemoryFhirRepository implements Repository {

Expand Down Expand Up @@ -111,7 +114,7 @@ public <T extends IBaseResource> MethodOutcome update(T resource, Map<String, St
@Override
public <T extends IBaseResource, I extends IIdType> MethodOutcome delete(
Class<T> resourceType, I id, Map<String, String> headers) {
var outcome = new MethodOutcome();
var outcome = new MethodOutcome(id, false);
var resources = resourceMap.computeIfAbsent(id.getResourceType(), r -> new HashMap<>());
var keyId = id.toUnqualifiedVersionless();
if (resources.containsKey(keyId)) {
Expand Down Expand Up @@ -226,8 +229,21 @@ public static <B extends IBaseBundle> B transactionStub(B transaction, Repositor
returnBundle,
BundleHelper.newEntryWithResponse(
version, BundleHelper.newResponseWithLocation(version, location)));
} else if (BundleHelper.isEntryRequestDelete(version, e)) {
if (BundleHelper.getEntryRequestId(version, e).isPresent()) {
var resourceType = Canonicals.getResourceType(
((BundleEntryComponent) e).getRequest().getUrl());
var resourceClass = SearchHelper.getResourceClass(repository, resourceType);
var res = repository.delete(
resourceClass,
BundleHelper.getEntryRequestId(version, e).get().withResourceType(resourceType));
BundleHelper.addEntry(returnBundle, BundleHelper.newEntryWithResource(version, res.getResource()));
} else {
throw new ResourceNotFoundException("Trying to delete an entry without id");
}

} else {
throw new NotImplementedOperationException("Transaction stub only supports PUT or POST");
throw new NotImplementedOperationException("Transaction stub only supports PUT, POST or DELETE");
}
});
return returnBundle;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package org.opencds.cqf.fhir.utility.visitor;

import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IDomainResource;
import org.opencds.cqf.fhir.api.Repository;
import org.opencds.cqf.fhir.utility.BundleHelper;
import org.opencds.cqf.fhir.utility.PackageHelper;
import org.opencds.cqf.fhir.utility.adapter.KnowledgeArtifactAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class WithdrawVisitor implements KnowledgeArtifactVisitor {
private Logger log = LoggerFactory.getLogger(WithdrawVisitor.class);
String isOwnedUrl = "http://hl7.org/fhir/StructureDefinition/crmi-isOwned";

@Override
public IBase visit(
KnowledgeArtifactAdapter rootAdapter, Repository repository, IBaseParameters operationParameters) {
if (!rootAdapter.getStatus().equals("draft")) {
throw new PreconditionFailedException("Cannot withdraw an artifact that is not in draft status");
}
var resToUpdate = new ArrayList<IDomainResource>();
resToUpdate.add(rootAdapter.get());

var resourcesToUpdate = getComponents(rootAdapter, repository, resToUpdate);

var fhirVersion = rootAdapter.get().getStructureFhirVersionEnum();

var transactionBundle = BundleHelper.newBundle(fhirVersion, null, "transaction");
for (var artifact : resourcesToUpdate) {
var entry = PackageHelper.deleteEntry(artifact);
BundleHelper.addEntry(transactionBundle, entry);
}

return repository.transaction(transactionBundle);
}

private List<IDomainResource> getComponents(
KnowledgeArtifactAdapter adapter, Repository repository, ArrayList<IDomainResource> resourcesToUpdate) {
adapter.getRelatedArtifactsOfType("composed-of").stream().forEach(c -> {
final var preReleaseReference = KnowledgeArtifactAdapter.getRelatedArtifactReference(c);
Optional<KnowledgeArtifactAdapter> maybeArtifact =
VisitorHelper.tryGetLatestVersion(preReleaseReference, repository);
if (maybeArtifact.isPresent()) {
if (resourcesToUpdate.stream()
.filter(rtu ->
rtu.getId().equals(maybeArtifact.get().getId().toString())
&& (rtu.getExtension().stream()
.anyMatch(ext -> ext.getUrl().equals(isOwnedUrl))))
.collect(Collectors.toList())
.isEmpty()) {
resourcesToUpdate.add(maybeArtifact.get().get());
getComponents(maybeArtifact.get(), repository, resourcesToUpdate);
}
}
});

return resourcesToUpdate;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@

import ca.uhn.fhir.context.FhirVersionEnum;
import java.util.Collections;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r4.model.Bundle.BundleEntryRequestComponent;
import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
import org.hl7.fhir.r4.model.IdType;
import org.junit.jupiter.api.Test;

class BundleHelperTests {
Expand Down Expand Up @@ -106,4 +111,103 @@ void r5() {
assertEquals(resource, getEntryResource(fhirVersion, entry));
assertFalse(getEntryResources(bundle).isEmpty());
}

@Test
void isEntryRequestDeleteDstu3() {
org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent bundle =
new org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent()
.setRequest(new org.hl7.fhir.dstu3.model.Bundle.BundleEntryRequestComponent()
.setMethod(org.hl7.fhir.dstu3.model.Bundle.HTTPVerb.DELETE));
var res = BundleHelper.isEntryRequestDelete(FhirVersionEnum.DSTU3, bundle);

assertTrue(res);
}

@Test
void isEntryRequestDeleteR4() {
BundleEntryComponent bundle = new Bundle.BundleEntryComponent()
.setRequest(new BundleEntryRequestComponent().setMethod(HTTPVerb.DELETE));
var res = BundleHelper.isEntryRequestDelete(FhirVersionEnum.R4, bundle);

assertTrue(res);
}

@Test
void isEntryRequestDeleteR5() {
org.hl7.fhir.r5.model.Bundle.BundleEntryComponent bundle =
new org.hl7.fhir.r5.model.Bundle.BundleEntryComponent()
.setRequest(new org.hl7.fhir.r5.model.Bundle.BundleEntryRequestComponent()
.setMethod(org.hl7.fhir.r5.model.Bundle.HTTPVerb.DELETE));
var res = BundleHelper.isEntryRequestDelete(FhirVersionEnum.R5, bundle);

assertTrue(res);
}

@Test
void isEntryRequestDeleteUnsupported() {
try {
org.hl7.fhir.r5.model.Bundle.BundleEntryComponent bundle =
new org.hl7.fhir.r5.model.Bundle.BundleEntryComponent()
.setRequest(new org.hl7.fhir.r5.model.Bundle.BundleEntryRequestComponent()
.setMethod(org.hl7.fhir.r5.model.Bundle.HTTPVerb.DELETE));

BundleHelper.isEntryRequestDelete(FhirVersionEnum.DSTU2, bundle);
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("Unsupported version of FHIR"));
}
}

@Test
void getEntryRequestIdDstu3() {
org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent bundle =
new org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent()
.setRequest(new org.hl7.fhir.dstu3.model.Bundle.BundleEntryRequestComponent()
.setMethod(org.hl7.fhir.dstu3.model.Bundle.HTTPVerb.GET));

bundle.getRequest().setUrl("Library/123");

var res = BundleHelper.getEntryRequestId(FhirVersionEnum.DSTU3, bundle);

assertEquals(new org.hl7.fhir.dstu3.model.IdType("123"), res.get());
}

@Test
void getEntryRequestIdR4() {
BundleEntryComponent bundle =
new Bundle.BundleEntryComponent().setRequest(new BundleEntryRequestComponent().setMethod(HTTPVerb.GET));

bundle.getRequest().setUrl("Library/123");

var res = BundleHelper.getEntryRequestId(FhirVersionEnum.R4, bundle);

assertEquals(new IdType("123"), res.get());
}

@Test
void getEntryRequestIdR5() {
org.hl7.fhir.r5.model.Bundle.BundleEntryComponent bundle =
new org.hl7.fhir.r5.model.Bundle.BundleEntryComponent()
.setRequest(new org.hl7.fhir.r5.model.Bundle.BundleEntryRequestComponent()
.setMethod(org.hl7.fhir.r5.model.Bundle.HTTPVerb.GET));

bundle.getRequest().setUrl("Library/123");

var res = BundleHelper.getEntryRequestId(FhirVersionEnum.R5, bundle);

assertEquals(new org.hl7.fhir.r5.model.IdType("123"), res.get());
}

@Test
void getEntryRequestIdUnsupported() {
try {
org.hl7.fhir.r5.model.Bundle.BundleEntryComponent bundle =
new org.hl7.fhir.r5.model.Bundle.BundleEntryComponent()
.setRequest(new org.hl7.fhir.r5.model.Bundle.BundleEntryRequestComponent()
.setMethod(org.hl7.fhir.r5.model.Bundle.HTTPVerb.DELETE));

BundleHelper.getEntryRequestId(FhirVersionEnum.DSTU2, bundle);
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("Unsupported version of FHIR"));
}
}
}
Loading

0 comments on commit 068cc68

Please sign in to comment.