Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a Transaction Bundle to Create Measure and Library Resource #91

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}
Loading