diff --git a/.github/integration-test/basic-auth/docker-compose.yml b/.github/integration-test/basic-auth/docker-compose.yml
index b212f4a0..104f36a8 100644
--- a/.github/integration-test/basic-auth/docker-compose.yml
+++ b/.github/integration-test/basic-auth/docker-compose.yml
@@ -1,6 +1,6 @@
services:
data-store:
- image: "samply/blaze:0.30"
+ image: "samply/blaze:0.31"
healthcheck:
test: ["CMD-SHELL", "curl --fail -s http://localhost:8080/health"]
interval: "5s"
@@ -37,7 +37,7 @@ services:
depends_on:
- data-store
proxy:
- image: "nginx:1.27.2"
+ image: "nginx:1.27.3"
volumes:
- "./nginx.conf:/etc/nginx/nginx.conf"
- "./proxy.htpasswd:/etc/auth/.htpasswd"
diff --git a/.github/integration-test/no-auth-cohort-enabled/docker-compose.yml b/.github/integration-test/no-auth-cohort-enabled/docker-compose.yml
index 95236ab7..9d81c15e 100644
--- a/.github/integration-test/no-auth-cohort-enabled/docker-compose.yml
+++ b/.github/integration-test/no-auth-cohort-enabled/docker-compose.yml
@@ -1,6 +1,6 @@
services:
data-store:
- image: "samply/blaze:0.30"
+ image: "samply/blaze:0.31"
healthcheck:
test: ["CMD-SHELL", "curl --fail -s http://localhost:8080/health"]
interval: "5s"
diff --git a/.github/integration-test/no-auth/docker-compose.yml b/.github/integration-test/no-auth/docker-compose.yml
index 273c19e3..4d94b98a 100644
--- a/.github/integration-test/no-auth/docker-compose.yml
+++ b/.github/integration-test/no-auth/docker-compose.yml
@@ -1,6 +1,6 @@
services:
data-store:
- image: "samply/blaze:0.30"
+ image: "samply/blaze:0.31"
healthcheck:
test: ["CMD-SHELL", "curl --fail -s http://localhost:8080/health"]
interval: "5s"
diff --git a/.github/integration-test/oauth/docker-compose.yml b/.github/integration-test/oauth/docker-compose.yml
index a655ecb3..963ee08b 100644
--- a/.github/integration-test/oauth/docker-compose.yml
+++ b/.github/integration-test/oauth/docker-compose.yml
@@ -50,7 +50,7 @@ services:
volumes:
- "./realm-test.json:/opt/keycloak/data/import/realm-test.json"
proxy:
- image: "nginx:1.27.2"
+ image: "nginx:1.27.3"
healthcheck:
test: ["CMD-SHELL", "curl --fail -s http://localhost:8080"]
interval: "5s"
@@ -72,7 +72,7 @@ services:
keycloak:
condition: service_healthy
data-store:
- image: "samply/blaze:0.30"
+ image: "samply/blaze:0.31"
healthcheck:
test: ["CMD-SHELL", "curl --fail -s http://localhost:8080/health"]
interval: "5s"
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index eb60b19e..326d67d6 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -42,7 +42,7 @@ jobs:
run: mvn -B verify
- name: Upload coverage to Codecov
- uses: codecov/codecov-action@v4
+ uses: codecov/codecov-action@v5
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e84a492a..55b54ebc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# Changelog
+## v2.5.0
+
+### Enhancements
+
+* Add Query Tracing to Logging ([#240](https://github.com/medizininformatik-initiative/flare/issues/240))
+
+The full changelog can be found [here](https://github.com/medizininformatik-initiative/flare/milestone/16?closed=1).
+
## v2.4.1
### Enhancements
diff --git a/Dockerfile b/Dockerfile
index 2f8cc3d5..3a20cee5 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM eclipse-temurin:21.0.5_11-jre
+FROM eclipse-temurin:21.0.5_11-jre-jammy@sha256:5f8358c9d5615c18e95728e8b8528bda7ff40a7a5da2ac9a35b7a01f5d9b231a
RUN apt-get update && apt-get upgrade -y && \
apt-get purge wget libbinutils libctf0 libctf-nobfd0 libncurses6 -y && \
diff --git a/docker-compose.yml b/docker-compose.yml
index 4be96f1f..0c5575b3 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,6 +1,6 @@
services:
flare:
- image: ghcr.io/medizininformatik-initiative/flare:2.4.1
+ image: ghcr.io/medizininformatik-initiative/flare:2.5.0
ports:
- 8080:8080
volumes:
diff --git a/pom.xml b/pom.xml
index 9b9c451c..9721320d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,13 +7,13 @@
org.springframework.boot
spring-boot-starter-parent
- 3.4.0
+ 3.4.1
de.medizininformatik-initiative
flare
- 2.4.1
+ 2.5.0
Flare
Flare
@@ -61,7 +61,7 @@
org.rocksdb
rocksdbjni
- 9.7.3
+ 9.8.4
diff --git a/src/main/java/de/medizininformatikinitiative/flare/rest/IdGenerator.java b/src/main/java/de/medizininformatikinitiative/flare/rest/IdGenerator.java
new file mode 100644
index 00000000..faef6676
--- /dev/null
+++ b/src/main/java/de/medizininformatikinitiative/flare/rest/IdGenerator.java
@@ -0,0 +1,13 @@
+package de.medizininformatikinitiative.flare.rest;
+
+import org.springframework.stereotype.Component;
+
+import java.util.UUID;
+
+@Component
+public class IdGenerator {
+
+ public UUID generateRandom() {
+ return UUID.randomUUID();
+ }
+}
diff --git a/src/main/java/de/medizininformatikinitiative/flare/rest/QueryController.java b/src/main/java/de/medizininformatikinitiative/flare/rest/QueryController.java
index e2d26cb6..cca96302 100644
--- a/src/main/java/de/medizininformatikinitiative/flare/rest/QueryController.java
+++ b/src/main/java/de/medizininformatikinitiative/flare/rest/QueryController.java
@@ -17,9 +17,9 @@
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
-import java.util.Objects;
import java.util.Set;
+import static java.util.Objects.requireNonNull;
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
@@ -33,14 +33,15 @@ public class QueryController {
private static final Logger logger = LoggerFactory.getLogger(QueryController.class);
private final StructuredQueryService queryService;
+ private final IdGenerator queryIdGenerator;
- public QueryController(StructuredQueryService queryService) {
- this.queryService = Objects.requireNonNull(queryService);
+ public QueryController(StructuredQueryService queryService, IdGenerator queryIdGenerator) {
+ this.queryService = requireNonNull(queryService);
+ this.queryIdGenerator = requireNonNull(queryIdGenerator);
}
@Bean
public RouterFunction queryRouter(@Value("${flare.cohort.enabled}") boolean cohortEnabled) {
-
var route = route(POST("query/execute").and(accept(MEDIA_TYPE_SQ)), this::execute)
.andRoute(POST("query/translate").and(accept(MEDIA_TYPE_SQ)), this::translate);
@@ -53,40 +54,42 @@ public RouterFunction queryRouter(@Value("${flare.cohort.enabled
public Mono execute(ServerRequest request) {
var startNanoTime = System.nanoTime();
- logger.debug("Execute feasibility query");
+ var queryId = queryIdGenerator.generateRandom();
+ logger.debug("Execute feasibility query {}", queryId);
return request.bodyToMono(StructuredQuery.class)
- .flatMap(queryService::execute).map(Set::size)
+ .flatMap(query -> queryService.execute(queryId, query)).map(Set::size)
.flatMap(count -> {
- logger.debug("Finished feasibility query returning cohort size {} in {} seconds.", count,
+ logger.debug("Finished feasibility query {} returning cohort size {} in {} seconds.", queryId, count,
"%.1f".formatted(Util.durationSecondsSince(startNanoTime)));
return ok().bodyValue(count);
})
.onErrorResume(MappingException.class, e -> {
- logger.warn("Mapping error: {}", e.getMessage());
+ logger.warn("Mapping error in feasibility query {}: {}", queryId, e.getMessage());
return badRequest().bodyValue(new Error(e.getMessage()));
})
.onErrorResume(WebClientRequestException.class, e -> {
- logger.error("Service not available because of downstream web client errors: {}", e.getMessage());
+ logger.error("Service not available in feasibility query {} because of downstream web client errors: {}", queryId, e.getMessage());
return status(503).bodyValue(new Error(e.getMessage()));
});
}
public Mono executeCohort(ServerRequest request) {
var startNanoTime = System.nanoTime();
- logger.debug("Execute cohort query");
+ var queryId = queryIdGenerator.generateRandom();
+ logger.debug("Execute cohort query {}", queryId);
return request.bodyToMono(StructuredQuery.class)
- .flatMap(queryService::execute)
+ .flatMap(query -> queryService.execute(queryId, query))
.flatMap(population -> {
- logger.debug("Finished cohort query returning cohort of {} patient IDs in {} seconds.", population.size(),
+ logger.debug("Finished cohort query {} returning cohort of {} patient IDs in {} seconds.", queryId, population.size(),
"%.1f".formatted(Util.durationSecondsSince(startNanoTime)));
return ok().bodyValue(population);
})
.onErrorResume(MappingException.class, e -> {
- logger.warn("Mapping error: {}", e.getMessage());
+ logger.warn("Mapping error in cohort query {}: {}", queryId, e.getMessage());
return badRequest().bodyValue(new Error(e.getMessage()));
})
.onErrorResume(WebClientRequestException.class, e -> {
- logger.error("Service not available because of downstream web client errors: {}", e.getMessage());
+ logger.error("Service not available in cohort query {} because of downstream web client errors: {}", queryId, e.getMessage());
return status(503).bodyValue(new Error(e.getMessage()));
});
}
diff --git a/src/main/java/de/medizininformatikinitiative/flare/service/DataStore.java b/src/main/java/de/medizininformatikinitiative/flare/service/DataStore.java
index 653ec1a7..cc0f684f 100644
--- a/src/main/java/de/medizininformatikinitiative/flare/service/DataStore.java
+++ b/src/main/java/de/medizininformatikinitiative/flare/service/DataStore.java
@@ -21,6 +21,7 @@
import java.time.Clock;
import java.time.Duration;
import java.util.Objects;
+import java.util.UUID;
import java.util.stream.Collectors;
import static de.medizininformatikinitiative.flare.model.fhir.QueryParams.stringValue;
@@ -48,9 +49,9 @@ public void init() {
}
@Override
- public Mono execute(Query query, boolean ignoreCache) {
+ public Mono execute(UUID id, Query query, boolean ignoreCache) {
var startNanoTime = System.nanoTime();
- logger.debug("Execute query: {}", query);
+ logger.debug("Execute query as part of query {}: {}", id, query);
return client.post()
.uri("/{type}/_search", query.type())
.contentType(APPLICATION_FORM_URLENCODED)
@@ -66,9 +67,9 @@ public Mono execute(Query query, boolean ignoreCache) {
.flatMap(bundle -> Flux.fromStream(bundle.entry().stream().flatMap(e -> e.resource().patientId().stream())))
.collect(Collectors.toSet())
.map(patientIds -> Population.copyOf(patientIds).withCreated(clock.instant()))
- .doOnNext(p -> logger.debug("Finished query `{}` returning {} patients in {} seconds.", query, p.size(),
+ .doOnNext(p -> logger.debug("Finished query `{}` as part of query {} returning {} patients in {} seconds.", query, id, p.size(),
"%.1f".formatted(Util.durationSecondsSince(startNanoTime))))
- .doOnError(e -> logger.error("Error while executing query `{}`: {}", query, e.getMessage()));
+ .doOnError(e -> logger.error("Error while executing query `{}` as part of query {}: {}", query, id, e.getMessage()));
}
private static boolean shouldRetry(HttpStatusCode code) {
diff --git a/src/main/java/de/medizininformatikinitiative/flare/service/DiskCachingFhirQueryService.java b/src/main/java/de/medizininformatikinitiative/flare/service/DiskCachingFhirQueryService.java
index cffa8ea4..7d150acd 100644
--- a/src/main/java/de/medizininformatikinitiative/flare/service/DiskCachingFhirQueryService.java
+++ b/src/main/java/de/medizininformatikinitiative/flare/service/DiskCachingFhirQueryService.java
@@ -13,6 +13,7 @@
import java.nio.ByteBuffer;
import java.time.Clock;
import java.time.Duration;
+import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -54,16 +55,16 @@ public void init() throws RocksDBException {
}
@Override
- public Mono execute(Query query, boolean ignoreCache) {
+ public Mono execute(UUID id, Query query, boolean ignoreCache) {
if (ignoreCache) {
- return executeQuery(query, true);
+ return executeQuery(id, query, true);
} else {
- return internalGet(query).switchIfEmpty(Mono.defer(() -> executeQuery(query, false)));
+ return internalGet(query).switchIfEmpty(Mono.defer(() -> executeQuery(id, query, false)));
}
}
- private Mono executeQuery(Query query, boolean ignoreCache) {
- return fhirQueryService.execute(query, ignoreCache).doOnNext(population -> put(query, population));
+ private Mono executeQuery(UUID id, Query query, boolean ignoreCache) {
+ return fhirQueryService.execute(id, query, ignoreCache).doOnNext(population -> put(query, population));
}
private Mono internalGet(Query query) {
diff --git a/src/main/java/de/medizininformatikinitiative/flare/service/FhirQueryService.java b/src/main/java/de/medizininformatikinitiative/flare/service/FhirQueryService.java
index 7b50866e..8bbf0bb8 100644
--- a/src/main/java/de/medizininformatikinitiative/flare/service/FhirQueryService.java
+++ b/src/main/java/de/medizininformatikinitiative/flare/service/FhirQueryService.java
@@ -4,11 +4,28 @@
import de.medizininformatikinitiative.flare.model.fhir.Query;
import reactor.core.publisher.Mono;
+import java.util.UUID;
+
public interface FhirQueryService {
- Mono execute(Query query, boolean ignoreCache);
+ /**
+ * Executes {@code query}.
+ *
+ * @param id the ID of the query used for tracing purposes
+ * @param query the query to execute
+ * @param ignoreCache if the cache shouldn't be used
+ * @return the population result
+ */
+ Mono execute(UUID id, Query query, boolean ignoreCache);
- default Mono execute(Query query) {
- return execute(query, false);
+ /**
+ * Executes {@code query} using a potentially cache.
+ *
+ * @param id the ID of the query used for tracing purposes
+ * @param query the query to execute
+ * @return the population result
+ */
+ default Mono execute(UUID id, Query query) {
+ return execute(id, query, false);
}
}
diff --git a/src/main/java/de/medizininformatikinitiative/flare/service/MemCachingFhirQueryService.java b/src/main/java/de/medizininformatikinitiative/flare/service/MemCachingFhirQueryService.java
index 2b3983d0..fba907e1 100644
--- a/src/main/java/de/medizininformatikinitiative/flare/service/MemCachingFhirQueryService.java
+++ b/src/main/java/de/medizininformatikinitiative/flare/service/MemCachingFhirQueryService.java
@@ -12,6 +12,8 @@
import reactor.core.publisher.Mono;
import java.time.Duration;
+import java.util.Objects;
+import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
@@ -21,12 +23,12 @@ public class MemCachingFhirQueryService implements FhirQueryService {
private static final Logger logger = LoggerFactory.getLogger(MemCachingFhirQueryService.class);
- private static final Weigher WEIGHER = (key, value) ->
- key.toString().length() + value.memSize();
+ private static final Weigher WEIGHER = (key, value) ->
+ key.query.toString().length() + value.memSize();
private final FhirQueryService fhirQueryService;
private final Config config;
- private AsyncLoadingCache cache;
+ private AsyncLoadingCache cache;
public MemCachingFhirQueryService(FhirQueryService fhirQueryService, Config config) {
this.fhirQueryService = requireNonNull(fhirQueryService);
@@ -46,9 +48,9 @@ public void init() {
}
@Override
- public Mono execute(Query query, boolean ignoreCache) {
- logger.trace("Try loading population for query `{}` from memory.", query);
- return Mono.fromFuture(cache.get(query));
+ public Mono execute(UUID id, Query query, boolean ignoreCache) {
+ logger.trace("Try loading population for query `{}` part of query {} from memory.", query, id);
+ return Mono.fromFuture(cache.get(new QueryWrapper(id, query)));
}
public CacheStats stats() {
@@ -67,21 +69,43 @@ public CacheStats stats() {
public record Config(long sizeInMebibytes, Duration expire, Duration refresh) {
}
- private class CacheLoader implements AsyncCacheLoader {
+ public record CacheStats(long estimatedEntryCount, long maxMemoryMiB, long usedMemoryMiB, long hitCount,
+ long missCount, long evictionCount, long loadSuccessCount, long loadFailureCount,
+ long totalLoadTimeNanos) {
+ }
+
+ private record QueryWrapper(UUID id, Query query) {
+
+ private QueryWrapper {
+ requireNonNull(id);
+ requireNonNull(query);
+ }
@Override
- public CompletableFuture asyncLoad(Query query, Executor executor) {
- return fhirQueryService.execute(query).toFuture();
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass()) return false;
+ QueryWrapper that = (QueryWrapper) o;
+ return Objects.equals(query, that.query);
}
@Override
- public CompletableFuture asyncReload(Query query, Population oldValue, Executor executor) {
- return fhirQueryService.execute(query, true).toFuture();
+ public int hashCode() {
+ return Objects.hashCode(query);
}
}
- public record CacheStats(long estimatedEntryCount, long maxMemoryMiB, long usedMemoryMiB, long hitCount,
- long missCount, long evictionCount, long loadSuccessCount, long loadFailureCount,
- long totalLoadTimeNanos) {
+ private class CacheLoader implements AsyncCacheLoader {
+
+ @Override
+ public CompletableFuture asyncLoad(QueryWrapper query, Executor executor) {
+ logger.trace("Cache miss for query `{}` part of query {}.", query.query, query.id);
+ return fhirQueryService.execute(query.id, query.query).toFuture();
+ }
+
+ @Override
+ public CompletableFuture asyncReload(QueryWrapper query, Population oldValue, Executor executor) {
+ logger.trace("Refresh query `{}`.", query.query);
+ return fhirQueryService.execute(query.id, query.query, true).toFuture();
+ }
}
}
diff --git a/src/main/java/de/medizininformatikinitiative/flare/service/StructuredQueryService.java b/src/main/java/de/medizininformatikinitiative/flare/service/StructuredQueryService.java
index 035c6235..998b6a92 100644
--- a/src/main/java/de/medizininformatikinitiative/flare/service/StructuredQueryService.java
+++ b/src/main/java/de/medizininformatikinitiative/flare/service/StructuredQueryService.java
@@ -15,6 +15,8 @@
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
+import java.util.UUID;
+
import static de.medizininformatikinitiative.flare.model.translate.Operator.Name.UNION;
import static java.util.Objects.requireNonNull;
@@ -36,14 +38,15 @@ public StructuredQueryService(@Qualifier("memCachingFhirQueryService") FhirQuery
/**
* Executes {@code query} and returns the Population of Patient IDs.
*
+ * @param id the ID of the query used for tracing purposes
* @param query the query to execute
* @return the Patient IDs qualifying the criteria
*/
- public Mono execute(StructuredQuery query) {
- var includedPatients = query.inclusionCriteria().executeAndIntersection(this::executeUnionGroup)
+ public Mono execute(UUID id, StructuredQuery query) {
+ var includedPatients = query.inclusionCriteria().executeAndIntersection(group -> executeUnionGroup(id, group))
.defaultIfEmpty(Population.of());
var excludedPatients = query.exclusionCriteria().map(c -> c.map(CriterionGroup::wrapCriteria)
- .executeAndUnion(group -> group.executeAndIntersection(this::executeUnionGroup))
+ .executeAndUnion(group -> group.executeAndIntersection(group1 -> executeUnionGroup(id, group1)))
.defaultIfEmpty(Population.of()))
.orElse(Mono.just(Population.of()));
return includedPatients
@@ -51,14 +54,14 @@ public Mono execute(StructuredQuery query) {
}
- private Mono executeUnionGroup(CriterionGroup group) {
- return group.executeAndUnion(this::executeSingle);
+ private Mono executeUnionGroup(UUID id, CriterionGroup group) {
+ return group.executeAndUnion(criterion -> executeSingle(id, criterion));
}
- private Flux executeSingle(Criterion criterion) {
- logger.trace("Execute single criterion {}", criterion);
+ private Flux executeSingle(UUID id, Criterion criterion) {
+ logger.trace("Execute single criterion of query {}: {}", id, criterion);
return translator.toQuery(criterion)
- .either(Flux::error, queries -> Flux.fromIterable(queries).flatMap(fhirQueryService::execute));
+ .either(Flux::error, queries -> Flux.fromIterable(queries).flatMap(query -> fhirQueryService.execute(id, query)));
}
/**
diff --git a/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java b/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java
index 8ba19d52..ca2bd413 100644
--- a/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java
+++ b/src/test/java/de/medizininformatikinitiative/flare/rest/QueryControllerTest.java
@@ -15,21 +15,20 @@
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.MediaType;
-import org.springframework.test.context.event.annotation.BeforeTestExecution;
-import org.springframework.test.context.event.annotation.BeforeTestMethod;
-import org.springframework.test.context.transaction.BeforeTransaction;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.testcontainers.shaded.com.fasterxml.jackson.core.JsonProcessingException;
import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper;
import reactor.core.publisher.Mono;
import java.util.List;
+import java.util.UUID;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class QueryControllerTest {
+ static final UUID ID = UUID.randomUUID();
static final String PATIENT_ID = "patient-id-211701";
static final String PATIENT_ID_1 = "patient-id-1-131300";
static final MediaType MEDIA_TYPE_SQ = MediaType.valueOf("application/sq+json");
@@ -41,6 +40,9 @@ class QueryControllerTest {
@Mock
StructuredQueryService queryService;
+ @Mock
+ IdGenerator queryIdGenerator;
+
@InjectMocks
private QueryController controller;
@@ -53,7 +55,8 @@ void setUp() {
@Test
void execute() {
- when(queryService.execute(STRUCTURED_QUERY)).thenReturn(Mono.just(Population.of(PATIENT_ID)));
+ when(queryIdGenerator.generateRandom()).thenReturn(ID);
+ when(queryService.execute(ID, STRUCTURED_QUERY)).thenReturn(Mono.just(Population.of(PATIENT_ID)));
client.post()
.uri("/query/execute")
@@ -87,7 +90,8 @@ void execute() {
@Test
void executeCohort() throws JsonProcessingException {
- when(queryService.execute(STRUCTURED_QUERY)).thenReturn(Mono.just(Population.of(PATIENT_ID, PATIENT_ID_1)));
+ when(queryIdGenerator.generateRandom()).thenReturn(ID);
+ when(queryService.execute(ID, STRUCTURED_QUERY)).thenReturn(Mono.just(Population.of(PATIENT_ID, PATIENT_ID_1)));
ObjectMapper objectMapper = new ObjectMapper();
@@ -123,7 +127,6 @@ void executeCohort() throws JsonProcessingException {
@Test
void executeCohortDisabled() {
-
client = WebTestClient.bindToRouterFunction(controller.queryRouter(false)).build();
client.post()
@@ -157,7 +160,8 @@ void executeCohortDisabled() {
@Test
void execute_error() {
- when(queryService.execute(STRUCTURED_QUERY)).thenReturn(Mono.error(new MappingNotFoundException(ContextualTermCode.of(TestUtil.CONTEXT, FEVER))));
+ when(queryIdGenerator.generateRandom()).thenReturn(ID);
+ when(queryService.execute(ID, STRUCTURED_QUERY)).thenReturn(Mono.error(new MappingNotFoundException(ContextualTermCode.of(TestUtil.CONTEXT, FEVER))));
client.post()
.uri("/query/execute")
diff --git a/src/test/java/de/medizininformatikinitiative/flare/service/DataStoreIT.java b/src/test/java/de/medizininformatikinitiative/flare/service/DataStoreIT.java
index 98f2e1aa..b7b99bd3 100644
--- a/src/test/java/de/medizininformatikinitiative/flare/service/DataStoreIT.java
+++ b/src/test/java/de/medizininformatikinitiative/flare/service/DataStoreIT.java
@@ -23,6 +23,7 @@
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneOffset;
+import java.util.UUID;
import static org.springframework.http.MediaType.APPLICATION_JSON;
@@ -31,6 +32,7 @@ class DataStoreIT {
private static final Logger logger = LoggerFactory.getLogger(DataStoreIT.class);
+ private static final UUID ID = UUID.randomUUID();
private static final Instant FIXED_INSTANT = Instant.ofEpochSecond(104152);
@Container
@@ -64,7 +66,7 @@ void setUp() {
@Test
void execute_empty() {
- var result = dataStore.execute(Query.ofType("Observation"));
+ var result = dataStore.execute(ID, Query.ofType("Observation"));
StepVerifier.create(result).expectNext(Population.of().withCreated(FIXED_INSTANT)).verifyComplete();
}
@@ -74,7 +76,7 @@ void execute_oneObservation() {
createPatient("0");
createObservation("0");
- var result = dataStore.execute(Query.ofType("Observation"));
+ var result = dataStore.execute(ID, Query.ofType("Observation"));
StepVerifier.create(result).expectNext(Population.of("0").withCreated(FIXED_INSTANT)).verifyComplete();
}
@@ -85,7 +87,7 @@ void execute_twoObservationsFromOnePatient() {
createObservation("0");
createObservation("0");
- var result = dataStore.execute(Query.ofType("Observation"));
+ var result = dataStore.execute(ID, Query.ofType("Observation"));
StepVerifier.create(result).expectNext(Population.of("0").withCreated(FIXED_INSTANT)).verifyComplete();
}
@@ -97,7 +99,7 @@ void execute_twoObservationsFromTwoPatients() {
createObservation("0");
createObservation("1");
- var result = dataStore.execute(Query.ofType("Observation"));
+ var result = dataStore.execute(ID, Query.ofType("Observation"));
StepVerifier.create(result).expectNext(Population.of("0", "1").withCreated(FIXED_INSTANT)).verifyComplete();
}
@@ -107,7 +109,7 @@ void execute_twoObservationsFromTwoPatients() {
void execute_OneObsWithoutReference_FromOnePat() {
createObservation_withoutReference();
- var result = dataStore.execute(Query.ofType("Observation"));
+ var result = dataStore.execute(ID, Query.ofType("Observation"));
StepVerifier.create(result).expectNext(Population.of().withCreated(FIXED_INSTANT)).verifyComplete();
}
@@ -120,7 +122,7 @@ void execute_OneObsWithoutReferenceOneObsWithReference_FromTwoPats() {
createObservation_withoutReference();
createObservation("1");
- var result = dataStore.execute(Query.ofType("Observation"));
+ var result = dataStore.execute(ID, Query.ofType("Observation"));
StepVerifier.create(result).expectNext(Population.of("1").withCreated(FIXED_INSTANT)).verifyComplete();
}
@@ -130,7 +132,7 @@ void execute_OneObsWithoutReferenceOneObsWithReference_FromTwoPats() {
void pendingAcquireQueueReachedMaximum() {
createPatient("0");
- var result = Flux.range(1, 1000).flatMap(i -> dataStore.execute(Query.ofType("Patient"))).collectList();
+ var result = Flux.range(1, 1000).flatMap(i -> dataStore.execute(ID, Query.ofType("Patient"))).collectList();
StepVerifier.create(result).verifyErrorMessage("Pending acquire queue has reached its maximum size of 8");
}
diff --git a/src/test/java/de/medizininformatikinitiative/flare/service/DataStoreTest.java b/src/test/java/de/medizininformatikinitiative/flare/service/DataStoreTest.java
index ea4e43fb..2a16c2cd 100644
--- a/src/test/java/de/medizininformatikinitiative/flare/service/DataStoreTest.java
+++ b/src/test/java/de/medizininformatikinitiative/flare/service/DataStoreTest.java
@@ -15,9 +15,11 @@
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneOffset;
+import java.util.UUID;
class DataStoreTest {
+ private static final UUID ID = UUID.randomUUID();
private static final Instant FIXED_INSTANT = Instant.ofEpochSecond(104152);
private static MockWebServer mockStore;
@@ -50,7 +52,7 @@ void execute_retry(int statusCode) {
mockStore.enqueue(new MockResponse().setResponseCode(statusCode));
mockStore.enqueue(new MockResponse().setResponseCode(200));
- var result = dataStore.execute(Query.ofType("Observation"));
+ var result = dataStore.execute(ID, Query.ofType("Observation"));
StepVerifier.create(result).expectNext(Population.of().withCreated(FIXED_INSTANT)).verifyComplete();
}
@@ -64,7 +66,7 @@ void execute_retry_fails() {
mockStore.enqueue(new MockResponse().setResponseCode(500));
mockStore.enqueue(new MockResponse().setResponseCode(200));
- var result = dataStore.execute(Query.ofType("Observation"));
+ var result = dataStore.execute(ID, Query.ofType("Observation"));
StepVerifier.create(result).verifyErrorMessage("Retries exhausted: 3/3");
}
@@ -74,7 +76,7 @@ void execute_retry_fails() {
void execute_retry_400() {
mockStore.enqueue(new MockResponse().setResponseCode(400));
- var result = dataStore.execute(Query.ofType("Observation"));
+ var result = dataStore.execute(ID, Query.ofType("Observation"));
StepVerifier.create(result).verifyError(WebClientResponseException.BadRequest.class);
}
diff --git a/src/test/java/de/medizininformatikinitiative/flare/service/DiskCachingFhirQueryServiceTest.java b/src/test/java/de/medizininformatikinitiative/flare/service/DiskCachingFhirQueryServiceTest.java
index bc10efc1..22faf037 100644
--- a/src/test/java/de/medizininformatikinitiative/flare/service/DiskCachingFhirQueryServiceTest.java
+++ b/src/test/java/de/medizininformatikinitiative/flare/service/DiskCachingFhirQueryServiceTest.java
@@ -22,6 +22,7 @@
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
+import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -33,6 +34,7 @@
@ExtendWith(MockitoExtension.class)
class DiskCachingFhirQueryServiceTest {
+ static final UUID ID = UUID.randomUUID();
static final Query QUERY = Query.ofType("foo");
static final String PATIENT_ID = "patient-id-113617";
static final String PATIENT_ID_1 = "patient-id-1-103010";
@@ -61,9 +63,9 @@ void tearDown() {
@Test
void execute_error() {
- when(queryService.execute(QUERY, false)).thenReturn(Mono.error(new Exception(ERROR_MSG)));
+ when(queryService.execute(ID, QUERY, false)).thenReturn(Mono.error(new Exception(ERROR_MSG)));
- var result = service.execute(QUERY);
+ var result = service.execute(ID, QUERY);
StepVerifier.create(result).verifyErrorMessage(ERROR_MSG);
waitForTasksToFinish();
@@ -72,9 +74,9 @@ void execute_error() {
@Test
void execute_miss() {
- when(queryService.execute(QUERY, false)).thenReturn(Mono.just(Population.of(PATIENT_ID)));
+ when(queryService.execute(ID, QUERY, false)).thenReturn(Mono.just(Population.of(PATIENT_ID)));
- var result = service.execute(QUERY);
+ var result = service.execute(ID, QUERY);
StepVerifier.create(result).expectNext(Population.of(PATIENT_ID)).verifyComplete();
waitForTasksToFinish();
@@ -85,7 +87,7 @@ void execute_miss() {
void execute_hit() {
ensureCacheContains(QUERY, Population.of(PATIENT_ID));
- var result = service.execute(QUERY);
+ var result = service.execute(ID, QUERY);
StepVerifier.create(result).expectNext(Population.of(PATIENT_ID)).verifyComplete();
waitForTasksToFinish();
@@ -95,9 +97,9 @@ void execute_hit() {
@Test
void execute_ignoringCache() {
ensureCacheContains(QUERY, Population.of(PATIENT_ID_1));
- when(queryService.execute(QUERY, true)).thenReturn(Mono.just(Population.of(PATIENT_ID_2)));
+ when(queryService.execute(ID, QUERY, true)).thenReturn(Mono.just(Population.of(PATIENT_ID_2)));
- var result = service.execute(QUERY, true);
+ var result = service.execute(ID, QUERY, true);
StepVerifier.create(result).expectNext(Population.of(PATIENT_ID_2)).verifyComplete();
waitForTasksToFinish();
@@ -108,9 +110,9 @@ void execute_ignoringCache() {
@DisplayName("expired populations will not be returned as hit")
void execute_expiredHit() {
ensureCacheContains(QUERY, Population.of(PATIENT_ID_1).withCreated(Instant.EPOCH.minus(2, MINUTES)));
- when(queryService.execute(QUERY, false)).thenReturn(Mono.just(Population.of(PATIENT_ID_2)));
+ when(queryService.execute(ID, QUERY, false)).thenReturn(Mono.just(Population.of(PATIENT_ID_2)));
- var result = service.execute(QUERY, false);
+ var result = service.execute(ID, QUERY, false);
StepVerifier.create(result).expectNext(Population.of(PATIENT_ID_2)).verifyComplete();
waitForTasksToFinish();
@@ -123,7 +125,7 @@ void execute_hit_largePopulation(int n) {
var population = populationOfSize(n);
ensureCacheContains(QUERY, population);
- var result = service.execute(QUERY);
+ var result = service.execute(ID, QUERY);
StepVerifier.create(result).expectNext(population).verifyComplete();
waitForTasksToFinish();
diff --git a/src/test/java/de/medizininformatikinitiative/flare/service/MemCachingFhirQueryServiceTest.java b/src/test/java/de/medizininformatikinitiative/flare/service/MemCachingFhirQueryServiceTest.java
index 1647c57b..67d97748 100644
--- a/src/test/java/de/medizininformatikinitiative/flare/service/MemCachingFhirQueryServiceTest.java
+++ b/src/test/java/de/medizininformatikinitiative/flare/service/MemCachingFhirQueryServiceTest.java
@@ -11,12 +11,14 @@
import reactor.test.StepVerifier;
import java.time.Duration;
+import java.util.UUID;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class MemCachingFhirQueryServiceTest {
+ static final UUID ID = UUID.randomUUID();
static final Query QUERY = Query.ofType("foo");
static final String PATIENT_ID = "patient-id-113003";
static final String ERROR_MSG = "error-msg-103632";
@@ -35,18 +37,18 @@ void setUp() {
@Test
void execute_error() {
- when(queryService.execute(QUERY)).thenReturn(Mono.error(new Exception(ERROR_MSG)));
+ when(queryService.execute(ID, QUERY)).thenReturn(Mono.error(new Exception(ERROR_MSG)));
- var result = service.execute(QUERY);
+ var result = service.execute(ID, QUERY);
StepVerifier.create(result).verifyErrorMessage(ERROR_MSG);
}
@Test
void execute_success() {
- when(queryService.execute(QUERY)).thenReturn(Mono.just(Population.of(PATIENT_ID)));
+ when(queryService.execute(ID, QUERY)).thenReturn(Mono.just(Population.of(PATIENT_ID)));
- var result = service.execute(QUERY);
+ var result = service.execute(ID, QUERY);
StepVerifier.create(result).expectNext(Population.of(PATIENT_ID)).verifyComplete();
}
@@ -56,12 +58,12 @@ void refresh() throws InterruptedException {
service = new MemCachingFhirQueryService(queryService, new MemCachingFhirQueryService.Config(128,
Duration.ofMinutes(1), Duration.ofMillis(100)));
service.init();
- when(queryService.execute(QUERY)).thenReturn(Mono.just(Population.of()));
- service.execute(QUERY);
- when(queryService.execute(QUERY, true)).thenReturn(Mono.just(Population.of(PATIENT_ID)));
+ when(queryService.execute(ID, QUERY)).thenReturn(Mono.just(Population.of()));
+ service.execute(ID, QUERY);
+ when(queryService.execute(ID, QUERY, true)).thenReturn(Mono.just(Population.of(PATIENT_ID)));
Thread.sleep(200);
- var result = service.execute(QUERY);
+ var result = service.execute(ID, QUERY);
StepVerifier.create(result).expectNext(Population.of(PATIENT_ID)).verifyComplete();
}
diff --git a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java
index c5b0ead9..86c11fda 100644
--- a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java
+++ b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceIT.java
@@ -41,6 +41,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
+import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -51,12 +52,13 @@
@SpringBootTest
class StructuredQueryServiceIT {
+ private static final Logger logger = LoggerFactory.getLogger(StructuredQueryServiceIT.class);
+
+ private static final UUID ID = UUID.randomUUID();
private static final Clock CLOCK_2000 = Clock.fixed(LocalDate.of(2000, 1, 1).atStartOfDay().toInstant(ZoneOffset.UTC), ZoneOffset.UTC);
private static final TermCode I08 = TermCode.of("http://fhir.de/CodeSystem/bfarm/icd-10-gm", "I08", "");
private static final TermCode DIAGNOSIS = TermCode.of("fdpg.mii.cds", "Diagnose", "Diagnose");
- private static final Logger logger = LoggerFactory.getLogger(StructuredQueryServiceIT.class);
-
@Container
@SuppressWarnings("resource")
private static final GenericContainer> blaze = new GenericContainer<>("samply/blaze:0.29")
@@ -130,7 +132,7 @@ void setUp() throws URISyntaxException, IOException {
void execute_Criterion() {
var query = StructuredQuery.of(CriterionGroup.of(CriterionGroup.of(Criterion.of(ContextualConcept.of(DIAGNOSIS, Concept.of(I08))))));
- var result = service.execute(query);
+ var result = service.execute(ID, query);
StepVerifier.create(result).expectNext(Population.of("id-pat-diag-I08.0")).verifyComplete();
}
@@ -139,7 +141,7 @@ void execute_Criterion() {
void execute_genderTestCase() throws URISyntaxException, IOException {
var query = parseSq(Files.readString(resourcePathFlareApplication("testCases").resolve("returningOther").resolve("2-gender.json")));
- var result = service.execute(query);
+ var result = service.execute(ID, query);
StepVerifier.create(result).expectNextMatches(p -> p.size() == 172).verifyComplete();
}
@@ -148,7 +150,7 @@ void execute_genderTestCase() throws URISyntaxException, IOException {
void execute_consentTestCase() throws URISyntaxException, IOException {
var query = parseSq(Files.readString(resourcePathFlareApplication("testCases").resolve("returningOther").resolve("consent.json")));
- var result = service.execute(query);
+ var result = service.execute(ID, query);
StepVerifier.create(result).expectNext(Population.of("id-pat-consent-test")).verifyComplete();
}
@@ -164,7 +166,7 @@ void execute_specimenTestCase() throws IOException, URISyntaxException {
var query = parseSq(slurpStructuredQueryService("referencedCriteria/sq-test-specimen-diag.json"));
- var result = service.execute(query);
+ var result = service.execute(ID, query);
StepVerifier.create(result).expectNext(Population.of("id-pat-diab-test-1")).verifyComplete();
@@ -173,7 +175,7 @@ void execute_specimenTestCase() throws IOException, URISyntaxException {
@ParameterizedTest
@MethodSource("getTestQueriesReturningOnePatient")
void execute_casesReturningOne(StructuredQuery query) {
- var result = service.execute(query);
+ var result = service.execute(ID, query);
StepVerifier.create(result).expectNextMatches(p -> p.size() == 1).verifyComplete();
}
@@ -221,7 +223,7 @@ void execute_BloodPressureTestCase() throws Exception {
}
""");
- var result = service_BloodPressure.execute(query);
+ var result = service_BloodPressure.execute(ID, query);
StepVerifier.create(result).expectNext(Population.of("id-pat-bloodpressure-test")).verifyComplete();
diff --git a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java
index 5ce6a4cc..347a7e4e 100644
--- a/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java
+++ b/src/test/java/de/medizininformatikinitiative/flare/service/StructuredQueryServiceTest.java
@@ -31,6 +31,7 @@
@ExtendWith(MockitoExtension.class)
class StructuredQueryServiceTest {
+ static final UUID ID = UUID.randomUUID();
static final String BFARM = "http://fhir.de/CodeSystem/bfarm/icd-10-gm";
static final Criterion CONCEPT_CRITERION = Criterion.of(cc(1));
static final Population EMPTY_POP = Population.of();
@@ -63,7 +64,7 @@ void setUp() {
@Test
void execute() {
- var result = service.execute(query);
+ var result = service.execute(ID, query);
StepVerifier.create(result).verifyError(MappingNotFoundException.class);
}
@@ -85,7 +86,7 @@ class SingleExpansion {
void execute() {
var query = query(incl(PATIENT_POP));
- var result = service.execute(query);
+ var result = service.execute(ID, query);
StepVerifier.create(result).expectNext(PATIENT_POP).verifyComplete();
@@ -111,7 +112,7 @@ class MultipleExpansion {
void execute_MultiplePatients() {
var query = query(inclExpand(PATIENT_1_POP, PATIENT_2_POP));
- var result = service.execute(query);
+ var result = service.execute(ID, query);
StepVerifier.create(result).expectNext(PATIENT_1_POP.union(PATIENT_2_POP)).verifyComplete();
}
@@ -121,7 +122,7 @@ void execute_MultiplePatients() {
void execute_SamePatient() {
var query = query(inclExpand(PATIENT_POP, PATIENT_POP));
- var result = service.execute(query);
+ var result = service.execute(ID, query);
StepVerifier.create(result).expectNext(PATIENT_POP).verifyComplete();
@@ -149,7 +150,7 @@ class ConjunctionInclusion {
void execute_MultiplePatients() {
var query = query(inclAnd(PATIENT_1_POP, PATIENT_2_POP));
- var result = service.execute(query);
+ var result = service.execute(ID, query);
StepVerifier.create(result).expectNext(EMPTY_POP).verifyComplete();
}
@@ -159,7 +160,7 @@ void execute_MultiplePatients() {
void execute_SamePatient() {
var query = query(inclAnd(PATIENT_POP, PATIENT_POP));
- var result = service.execute(query);
+ var result = service.execute(ID, query);
StepVerifier.create(result).expectNext(PATIENT_POP).verifyComplete();
}
@@ -184,7 +185,7 @@ class DisjunctionInclusion {
void execute_MultiplePatients() {
var query = query(inclOr(PATIENT_1_POP, PATIENT_2_POP));
- var result = service.execute(query);
+ var result = service.execute(ID, query);
StepVerifier.create(result).expectNext(PATIENT_1_POP.union(PATIENT_2_POP)).verifyComplete();
}
@@ -194,7 +195,7 @@ void execute_MultiplePatients() {
void execute_SamePatient() {
var query = query(inclOr(PATIENT_POP, PATIENT_POP));
- var result = service.execute(query);
+ var result = service.execute(ID, query);
StepVerifier.create(result).expectNext(PATIENT_POP).verifyComplete();
}
@@ -219,7 +220,7 @@ class SingleInclusionAndExclusion {
void execute_PatientNotExcluded() {
var query = query(incl(PATIENT_POP), excl(PATIENT_1_POP));
- var result = service.execute(query);
+ var result = service.execute(ID, query);
StepVerifier.create(result).expectNext(PATIENT_POP).verifyComplete();
}
@@ -229,7 +230,7 @@ void execute_PatientNotExcluded() {
void execute_PatientExcluded() {
var query = query(incl(PATIENT_POP), excl(PATIENT_POP));
- var result = service.execute(query);
+ var result = service.execute(ID, query);
StepVerifier.create(result).expectNext(EMPTY_POP).verifyComplete();
}
@@ -255,7 +256,7 @@ class SingleInclusionAndDisjunctionExclusion {
void execute_PatientNotExcluded() {
var query = query(incl(PATIENT_POP), exclOr(PATIENT_1_POP, PATIENT_2_POP));
- var result = service.execute(query);
+ var result = service.execute(ID, query);
StepVerifier.create(result).expectNext(PATIENT_POP).verifyComplete();
@@ -266,7 +267,7 @@ void execute_PatientNotExcluded() {
void execute_PatientExcluded() {
var query = query(incl(PATIENT_POP), exclOr(PATIENT_POP, PATIENT_1_POP));
- var result = service.execute(query);
+ var result = service.execute(ID, query);
StepVerifier.create(result).expectNext(EMPTY_POP).verifyComplete();
}
@@ -293,7 +294,7 @@ class SingleInclusionAndConjunctionExclusion {
void execute_PatientNotExcluded() {
var query = query(incl(PATIENT_POP), exclAnd(PATIENT_POP, PATIENT_1_POP));
- var result = service.execute(query);
+ var result = service.execute(ID, query);
StepVerifier.create(result).expectNext(PATIENT_POP).verifyComplete();
}
@@ -303,7 +304,7 @@ void execute_PatientNotExcluded() {
void execute_PatientExcluded() {
var query = query(incl(PATIENT_POP), exclAnd(PATIENT_POP, PATIENT_POP));
- var result = service.execute(query);
+ var result = service.execute(ID, query);
StepVerifier.create(result).expectNext(EMPTY_POP).verifyComplete();
}
@@ -348,15 +349,15 @@ CriterionGroup group(TermCode c1, TermCode c2) {
Criterion whenQuery(Population population) {
var criterionQuery = whenCriterion(TermCode.of(BFARM, UUID.randomUUID().toString(), ""));
- when(fhirQueryService.execute(criterionQuery.query)).thenReturn(Mono.just(population));
+ when(fhirQueryService.execute(ID, criterionQuery.query)).thenReturn(Mono.just(population));
return criterionQuery.criterion;
}
Criterion whenQueryExpand(Population p1, Population p2) {
var criterionQuery = whenCriterionExpand(TermCode.of(BFARM, UUID.randomUUID().toString(), ""),
TermCode.of(BFARM, UUID.randomUUID().toString(), ""));
- when(fhirQueryService.execute(criterionQuery.query1)).thenReturn(Mono.just(p1));
- when(fhirQueryService.execute(criterionQuery.query2)).thenReturn(Mono.just(p2));
+ when(fhirQueryService.execute(ID, criterionQuery.query1)).thenReturn(Mono.just(p1));
+ when(fhirQueryService.execute(ID, criterionQuery.query2)).thenReturn(Mono.just(p2));
return criterionQuery.criterion;
}