Skip to content

Commit

Permalink
Use a Transaction Bundle to Create Measure and Library Resource
Browse files Browse the repository at this point in the history
Closes: #84
  • Loading branch information
alexanderkiel authored and EmteZogaf committed May 31, 2024
1 parent 8567d7b commit fbc7f20
Show file tree
Hide file tree
Showing 9 changed files with 206 additions and 40 deletions.
4 changes: 2 additions & 2 deletions mii-process-feasibility-docker-test-setup/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,7 @@ services:

# ---- DIC-2 - FHIR Data Store ----------------------------------------------
dic-2-store:
image: samply/blaze:0.22.3
image: samply/blaze:0.24
restart: on-failure
healthcheck:
test: [ "CMD", "curl", "http://localhost:8080/health" ]
Expand Down Expand Up @@ -739,7 +739,7 @@ services:

# ---- DIC-3 - FHIR Data Store ----------------------------------------------
dic-3-store:
image: samply/blaze:0.22.3
image: samply/blaze:0.24
restart: on-failure
healthcheck:
test: [ "CMD", "curl", "http://localhost:8080/health" ]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
package de.medizininformatik_initiative.process.feasibility.service;

import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import de.medizininformatik_initiative.process.feasibility.variables.ConstantsFeasibility;
import dev.dsf.bpe.v1.ProcessPluginApi;
import dev.dsf.bpe.v1.activity.AbstractServiceDelegate;
import dev.dsf.bpe.v1.variables.Variables;
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Library;
import org.hl7.fhir.r4.model.Measure;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;

import java.util.ArrayList;
import java.util.Objects;
import java.util.regex.Pattern;

import static de.medizininformatik_initiative.process.feasibility.variables.ConstantsFeasibility.VARIABLE_LIBRARY;
import static de.medizininformatik_initiative.process.feasibility.variables.ConstantsFeasibility.VARIABLE_MEASURE;
import static de.medizininformatik_initiative.process.feasibility.variables.ConstantsFeasibility.*;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hl7.fhir.r4.model.Bundle.BundleType.TRANSACTION;
import static org.hl7.fhir.r4.model.Bundle.HTTPVerb.POST;

public class StoreFeasibilityResources extends AbstractServiceDelegate implements InitializingBean {

private static final Logger logger = LoggerFactory.getLogger(StoreFeasibilityResources.class);
private static final Pattern MEASURE_URL_PATTERN = Pattern.compile("(.+)/Measure/(.+)");
private static final Pattern LIBRARY_URL_PATTERN = Pattern.compile("urn:uuid:(.+)");

private final IGenericClient storeClient;
private final FeasibilityResourceCleaner cleaner;
Expand Down Expand Up @@ -49,21 +54,43 @@ protected void doExecute(DelegateExecution execution, Variables variables) {
cleaner.cleanLibrary(library);
cleaner.cleanMeasure(measure);

var libraryRes = storeLibraryResource(library);
var measureRes = storeMeasureResource(measure, libraryRes.getId());
fixCanonical(measure, library);

variables.setString(ConstantsFeasibility.VARIABLE_MEASURE_ID, measureRes.getId().getIdPart());
var transactionResponse = storeResources(measure, library);

variables.setString(VARIABLE_MEASURE_ID, extractMeasureId(transactionResponse));
}

private void fixCanonical(Measure measure, Library library) {
var measureUrlMatcher = MEASURE_URL_PATTERN.matcher(measure.getUrl());
var libraryUrlMatcher = LIBRARY_URL_PATTERN.matcher(library.getUrl());
if (measureUrlMatcher.find() && libraryUrlMatcher.find()) {
var base = measureUrlMatcher.group(1);
var measureId = measureUrlMatcher.group(2);
var libraryId = libraryUrlMatcher.group(1);
var libraryUrl = base + "/Library/" + libraryId;
measure.setLibrary(new ArrayList<>());
measure.addLibrary(libraryUrl);
library.setUrl(libraryUrl);
library.setName(libraryId);
library.setVersion("1.0.0");
var data = new String(library.getContent().get(0).getData(), UTF_8);
var rest = data.split("\n", 2)[1];
var newData = "library \"%s\" version '1.0.0'\n".formatted(libraryId) + rest;
library.getContent().get(0).setData(newData.getBytes(UTF_8));
}
}

private MethodOutcome storeLibraryResource(Library library) {
logger.info("Store Library `{}`", library.getId());
return storeClient.create().resource(library).execute();
private Bundle storeResources(Measure measure, Library library) {
logger.info("Store Measure `{}` and Library `{}`", measure.getId(), library.getUrl());

Bundle bundle = new Bundle().setType(TRANSACTION);
bundle.addEntry().setResource(measure).getRequest().setMethod(POST).setUrl("Measure");
bundle.addEntry().setResource(library).getRequest().setMethod(POST).setUrl("Library");
return storeClient.transaction().withBundle(bundle).execute();
}

private MethodOutcome storeMeasureResource(Measure measure, IIdType libraryId) {
logger.info("Store Measure `{}`", measure.getId());
measure.getLibrary().clear();
measure.addLibrary("Library/" + libraryId.getIdPart());
return storeClient.create().resource(measure).execute();
private String extractMeasureId(Bundle transactionResponse) {
return new IdType(transactionResponse.getEntryFirstRep().getResponse().getLocation()).getIdPart();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package de.medizininformatik_initiative.process.feasibility;

import org.hl7.fhir.r4.model.Base;
import org.hl7.fhir.r4.model.Bundle;

public interface Assertions {

static BaseAssert assertThat(Base actual) {
return new BaseAssert(actual);
}

static BundleAssert assertThat(Bundle actual) {
return new BundleAssert(actual);
}

static BundleEntryRequestComponentAssert assertThat(Bundle.BundleEntryRequestComponent actual) {
return new BundleEntryRequestComponentAssert(actual);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package de.medizininformatik_initiative.process.feasibility;

import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.Condition;
import org.hl7.fhir.r4.model.Base;

public class BaseAssert extends AbstractAssert<BaseAssert, Base> {

protected BaseAssert(Base actual) {
super(actual, BaseAssert.class);
}

private static Condition<Base> deepEqualTo(Base expected) {
return new Condition<>(actual -> actual.equalsDeep(expected), "deep equal to " + expected);
}

public BaseAssert isDeepEqualTo(Base expected) {
return is(deepEqualTo(expected));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package de.medizininformatik_initiative.process.feasibility;

import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.Condition;
import org.hl7.fhir.r4.model.Bundle;

public class BundleAssert extends AbstractAssert<BundleAssert, Bundle> {

protected BundleAssert(Bundle actual) {
super(actual, BundleAssert.class);
}

public BundleAssert hasType(String type) {
return has(type(type));
}

private static Condition<Bundle> type(String type) {
return new Condition<>(bundle -> bundle.getType().toCode().equals(type), "of type " + type);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package de.medizininformatik_initiative.process.feasibility;

import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.Condition;
import org.hl7.fhir.r4.model.Bundle;


public class BundleEntryRequestComponentAssert extends AbstractAssert<BundleEntryRequestComponentAssert,
Bundle.BundleEntryRequestComponent> {

protected BundleEntryRequestComponentAssert(Bundle.BundleEntryRequestComponent actual) {
super(actual, BundleEntryRequestComponentAssert.class);
}

public BundleEntryRequestComponentAssert hasMethod(Bundle.HTTPVerb method) {
return has(method(method));
}

private static Condition<Bundle.BundleEntryRequestComponent> method(Bundle.HTTPVerb method) {
return new Condition<>(bundle -> bundle.getMethod() == method, "of method " + method);
}

public BundleEntryRequestComponentAssert hasUrl(String url) {
return has(url(url));
}

private static Condition<Bundle.BundleEntryRequestComponent> url(String url) {
return new Condition<>(bundle -> bundle.getUrl().equals(url), "of URL " + url);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public abstract class FlareWebserviceClientImplBaseIT {

protected static final Network DEFAULT_CONTAINER_NETWORK = Network.newNetwork();

public static GenericContainer<?> fhirServer = new GenericContainer<>(DockerImageName.parse("samply/blaze:0.23.0"))
public static GenericContainer<?> fhirServer = new GenericContainer<>(DockerImageName.parse("samply/blaze:0.24"))
.withExposedPorts(8080)
.withNetwork(DEFAULT_CONTAINER_NETWORK)
.withNetworkAliases("fhir-server")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public class StoreClientIT {
private static final Network DEFAULT_CONTAINER_NETWORK = Network.newNetwork();

@Container
public GenericContainer<?> fhirServer = new GenericContainer<>(DockerImageName.parse("samply/blaze:0.23.0"))
public GenericContainer<?> fhirServer = new GenericContainer<>(DockerImageName.parse("samply/blaze:0.24"))
.withExposedPorts(8080)
.withNetwork(DEFAULT_CONTAINER_NETWORK)
.withNetworkAliases("fhir-server")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
package de.medizininformatik_initiative.process.feasibility.service;

import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import dev.dsf.bpe.v1.variables.Variables;
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Library;
import org.hl7.fhir.r4.model.Measure;
import org.hl7.fhir.r4.model.Resource;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;

import static de.medizininformatik_initiative.process.feasibility.Assertions.assertThat;
import static de.medizininformatik_initiative.process.feasibility.variables.ConstantsFeasibility.*;
import static org.mockito.ArgumentMatchers.any;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hl7.fhir.r4.model.Bundle.HTTPVerb.POST;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

Expand All @@ -32,6 +31,9 @@ public class StoreFeasibilityResourcesTest {
@Mock
private FeasibilityResourceCleaner cleaner;

@Captor
private ArgumentCaptor<Bundle> transactionBundleCaptor;

@Mock
private DelegateExecution execution;

Expand All @@ -41,24 +43,72 @@ public class StoreFeasibilityResourcesTest {
@InjectMocks
private StoreFeasibilityResources service;

@Test
public void testDoExecute() {
var measure = new Measure();
var library = new Library();
library.getContentFirstRep().setContentType("text/cql");
when(variables.getResource(VARIABLE_MEASURE)).thenReturn(measure);
when(variables.getResource(VARIABLE_LIBRARY)).thenReturn(library);
// Creates a Measure like the Feasibility Backend will do.
private static Measure inputMeasure() {
return new Measure()
.setUrl("https://foo.de/Measure/9308b842-2bee-418b-b3bf-cc347541c1c3")
.addLibrary("urn:uuid:7942465b-513a-4812-a078-e72dfea97f43");
}

var libraryMethodOutcome = new MethodOutcome(new IdType(LIBRARY_ID));
var measureMethodOutcome = new MethodOutcome(new IdType(MEASURE_ID));
// Creates a Library like the Feasibility Backend will do.
private static Library inputLibrary() {
var library = new Library()
.setUrl("urn:uuid:7942465b-513a-4812-a078-e72dfea97f43")
.setName("Retrieve");
library.addContent().setContentType("text/cql").setData("""
library Retrieve version '1.0.0'
using FHIR version '4.0.0'
include FHIRHelpers version '4.0.0'
""".getBytes(UTF_8));
return library;
}

when(storeClient.create().resource(any(Resource.class)).execute())
.thenReturn(libraryMethodOutcome, measureMethodOutcome);
private static Measure outputMeasure() {
return new Measure()
.setUrl("https://foo.de/Measure/9308b842-2bee-418b-b3bf-cc347541c1c3")
.addLibrary("https://foo.de/Library/7942465b-513a-4812-a078-e72dfea97f43");
}

private static Library outputLibrary() {
var library = new Library()
.setUrl("https://foo.de/Library/7942465b-513a-4812-a078-e72dfea97f43")
.setName("7942465b-513a-4812-a078-e72dfea97f43")
.setVersion("1.0.0");
library.addContent().setContentType("text/cql").setData("""
library "7942465b-513a-4812-a078-e72dfea97f43" version '1.0.0'
using FHIR version '4.0.0'
include FHIRHelpers version '4.0.0'
""".getBytes(UTF_8));
return library;
}

@Test
public void testDoExecute() {
var inputMeasure = inputMeasure();
var inputLibrary = inputLibrary();
var transactionResponse = new Bundle();
transactionResponse.addEntry().getResponse().setLocation("Measure/" + MEASURE_ID);
inputLibrary.getContentFirstRep().setContentType("text/cql");
when(variables.getResource(VARIABLE_MEASURE)).thenReturn(inputMeasure);
when(variables.getResource(VARIABLE_LIBRARY)).thenReturn(inputLibrary);
when(storeClient.transaction().withBundle(transactionBundleCaptor.capture()).execute()).thenReturn(transactionResponse);

service.doExecute(execution, variables);

verify(cleaner).cleanLibrary(library);
verify(cleaner).cleanMeasure(measure);
verify(cleaner).cleanLibrary(inputLibrary);
verify(cleaner).cleanMeasure(inputMeasure);
assertThat(transactionBundleCaptor.getValue()).hasType("transaction");
assertThat(transactionBundleCaptor.getValue().getEntry()).hasSize(2);
assertThat(transactionBundleCaptor.getValue().getEntry().get(0).getResource())
.isDeepEqualTo(outputMeasure());
assertThat(transactionBundleCaptor.getValue().getEntry().get(0).getRequest())
.hasMethod(POST)
.hasUrl("Measure");
assertThat(transactionBundleCaptor.getValue().getEntry().get(1).getResource())
.isDeepEqualTo(outputLibrary());
assertThat(transactionBundleCaptor.getValue().getEntry().get(1).getRequest())
.hasMethod(POST)
.hasUrl("Library");
verify(variables).setString(VARIABLE_MEASURE_ID, MEASURE_ID);
}
}

0 comments on commit fbc7f20

Please sign in to comment.