From 88397619b61908a51450de7cc3bbd530f0be9f75 Mon Sep 17 00:00:00 2001 From: Thomas Papke Date: Sat, 2 Dec 2023 23:05:40 +0100 Subject: [PATCH] #1 Fix getAll integration tests due to missing support for multi resource query in hapi fhir --- .../registry/query/StoredQueryProcessor.java | 77 +++++----- .../registry/query/StoredQueryVistorImpl.java | 140 +++++++++++++----- .../registry/XdsToFhirApplicationIT.java | 8 +- .../query/StoredQueryVistorImplTest.java | 27 ++-- 4 files changed, 158 insertions(+), 94 deletions(-) diff --git a/src/main/java/org/openehealth/app/xdstofhir/registry/query/StoredQueryProcessor.java b/src/main/java/org/openehealth/app/xdstofhir/registry/query/StoredQueryProcessor.java index f7d2165..1920db0 100644 --- a/src/main/java/org/openehealth/app/xdstofhir/registry/query/StoredQueryProcessor.java +++ b/src/main/java/org/openehealth/app/xdstofhir/registry/query/StoredQueryProcessor.java @@ -1,24 +1,21 @@ package org.openehealth.app.xdstofhir.registry.query; +import java.util.ArrayList; import java.util.Collections; -import java.util.List; -import java.util.Objects; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; import ca.uhn.fhir.rest.client.api.IGenericClient; -import ca.uhn.fhir.util.BundleUtil; import lombok.RequiredArgsConstructor; -import org.hl7.fhir.instance.model.api.IBaseBundle; -import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.DocumentReference; import org.openehealth.app.xdstofhir.registry.common.fhir.MhdSubmissionSet; import org.openehealth.ipf.commons.ihe.xds.core.metadata.DocumentEntry; import org.openehealth.ipf.commons.ihe.xds.core.metadata.ObjectReference; import org.openehealth.ipf.commons.ihe.xds.core.metadata.SubmissionSet; import org.openehealth.ipf.commons.ihe.xds.core.metadata.Version; +import org.openehealth.ipf.commons.ihe.xds.core.metadata.XDSMetaClass; import org.openehealth.ipf.commons.ihe.xds.core.requests.QueryRegistry; import org.openehealth.ipf.commons.ihe.xds.core.requests.query.QueryReturnType; import org.openehealth.ipf.commons.ihe.xds.core.responses.ErrorCode; @@ -44,68 +41,62 @@ public QueryResponse processQuery(QueryRegistry query) { var visitor = new StoredQueryVistorImpl(client); query.getQuery().accept(visitor); - var resultBundle = visitor.getFhirQuery().execute(); - var response = new QueryResponse(Status.SUCCESS); - var xdsDocuments = getDocumentsFrom(resultBundle); - var submissionSets = getSubmissionSetsFrom(resultBundle); + var fhirDocuments = visitor.getDocumentsFromResult(); + var fhirSubmissions = visitor.getSubmissionSetsFrom(); + var xdsDocuments = new ArrayList(); + var xdsSubmissionSets = new ArrayList(); - while (resultBundle.getLink(IBaseBundle.LINK_NEXT) != null) { - resultBundle = client.loadPage().next(resultBundle).execute(); - xdsDocuments.addAll(getDocumentsFrom(resultBundle)); - submissionSets.addAll(getSubmissionSetsFrom(resultBundle)); + for (DocumentReference document : fhirDocuments) { if (xdsDocuments.size() > maxResultCount) { response.setStatus(Status.PARTIAL_SUCCESS); response.setErrors(Collections.singletonList(new ErrorInfo(ErrorCode.TOO_MANY_RESULTS, "Result exceed maximum of " + maxResultCount, Severity.WARNING, null, null))); break; } + var xdsDoc = documentMapper.apply(document); + if (xdsDoc != null) { + assignDefaultVersioning().accept(xdsDoc); + xdsDocuments.add(xdsDoc); + } + + } + + for (MhdSubmissionSet submissionset : fhirSubmissions) { + if (xdsDocuments.size() + xdsSubmissionSets.size() > maxResultCount) { + response.setStatus(Status.PARTIAL_SUCCESS); + response.setErrors(Collections.singletonList(new ErrorInfo(ErrorCode.TOO_MANY_RESULTS, + "Result exceed maximum of " + maxResultCount, Severity.WARNING, null, null))); + break; + } + var xdsSubmission = submissionMapper.apply(submissionset); + if (xdsSubmission != null) { + assignDefaultVersioning().accept(xdsSubmission); + xdsSubmissionSets.add(xdsSubmission); + } } if (query.getReturnType().equals(QueryReturnType.LEAF_CLASS)) { response.setDocumentEntries(xdsDocuments); - response.setSubmissionSets(submissionSets); + response.setSubmissionSets(xdsSubmissionSets); } else { - response.setReferences(Stream.concat(xdsDocuments.stream(), submissionSets.stream()) - .map(xdsObject -> new ObjectReference(xdsObject.getEntryUuid())) - .collect(Collectors.toList())); + response.setReferences(Stream.concat(xdsDocuments.stream(), xdsSubmissionSets.stream()) + .map(xdsObject -> new ObjectReference(xdsObject.getEntryUuid())).collect(Collectors.toList())); } return response; } - - private List getDocumentsFrom(Bundle resultBundle) { - var listOfResources = BundleUtil.toListOfResourcesOfType(client.getFhirContext(), - resultBundle, DocumentReference.class); - var xdsDocuments = listOfResources.stream() - .map(documentMapper) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - xdsDocuments.forEach(assignDefaultVersioning()); - return xdsDocuments; - } - - private List getSubmissionSetsFrom(Bundle resultBundle) { - var listOfResources = BundleUtil.toListOfResourcesOfType(client.getFhirContext(), - resultBundle, MhdSubmissionSet.class); - var xdsSubmissions = listOfResources.stream() - .map(submissionMapper) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - return xdsSubmissions; - } - /** * ebRIM chapter 2.5.1 requires versionInfo and lid to be set. * * @return consumer setting proper defaults for lid and versionInfo */ - private Consumer assignDefaultVersioning() { - return doc -> { - doc.setLogicalUuid(doc.getEntryUuid()); - doc.setVersion(DEFAULT_VERSION); + private Consumer assignDefaultVersioning() { + return meta -> { + meta.setLogicalUuid(meta.getEntryUuid()); + meta.setVersion(DEFAULT_VERSION); }; } diff --git a/src/main/java/org/openehealth/app/xdstofhir/registry/query/StoredQueryVistorImpl.java b/src/main/java/org/openehealth/app/xdstofhir/registry/query/StoredQueryVistorImpl.java index 4e429d6..2c55555 100644 --- a/src/main/java/org/openehealth/app/xdstofhir/registry/query/StoredQueryVistorImpl.java +++ b/src/main/java/org/openehealth/app/xdstofhir/registry/query/StoredQueryVistorImpl.java @@ -5,21 +5,25 @@ import static org.openehealth.app.xdstofhir.registry.common.MappingSupport.toUrnCoded; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; +import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.stream.Collectors; -import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.gclient.DateClientParam; import ca.uhn.fhir.rest.gclient.IQuery; import ca.uhn.fhir.rest.gclient.TokenClientParam; -import com.google.common.collect.Lists; -import lombok.Getter; +import ca.uhn.fhir.util.BundleUtil; +import lombok.RequiredArgsConstructor; +import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.DocumentReference; +import org.hl7.fhir.r4.model.DomainResource; import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.codesystems.DocumentReferenceStatus; import org.openehealth.app.xdstofhir.registry.common.MappingSupport; @@ -57,38 +61,36 @@ import org.openehealth.ipf.commons.ihe.xds.core.requests.query.Query.Visitor; import org.openehealth.ipf.commons.ihe.xds.core.requests.query.QueryList; +@RequiredArgsConstructor public class StoredQueryVistorImpl implements Visitor { - @Getter - private IQuery fhirQuery; - private final IGenericClient client; + private IQuery documentFhirQuery; + private IQuery submissionSetfhirQuery; - public StoredQueryVistorImpl(IGenericClient client) { - this.client = client; - } + private final IGenericClient client; @Override public void visit(FindDocumentsQuery query) { - this.fhirQuery = client.search().forResource(DocumentReference.class) + this.documentFhirQuery = client.search().forResource(DocumentReference.class) .withProfile(MappingSupport.MHD_COMPREHENSIVE_PROFILE) .include(DocumentReference.INCLUDE_SUBJECT) .returnBundle(Bundle.class); - mapPatientIdToQuery(query); - - map(query.getClassCodes(), DocumentReference.CATEGORY); - map(query.getTypeCodes(),DocumentReference.TYPE); - map(query.getPracticeSettingCodes(),DocumentReference.SETTING); - map(query.getHealthcareFacilityTypeCodes(),DocumentReference.FACILITY); - map(query.getFormatCodes(),DocumentReference.FORMAT); - map(query.getStatus()); - map(query.getEventCodes(), DocumentReference.EVENT); - map(query.getConfidentialityCodes(), DocumentReference.SECURITY_LABEL); - map(query.getCreationTime(), DocumentReference.DATE); - map(query.getServiceStartTime(), DocumentReference.PERIOD); - map(query.getServiceStopTime(), DocumentReference.PERIOD); + mapPatientIdToQuery(query, documentFhirQuery); + + map(query.getClassCodes(), DocumentReference.CATEGORY, documentFhirQuery); + map(query.getTypeCodes(),DocumentReference.TYPE, documentFhirQuery); + map(query.getPracticeSettingCodes(),DocumentReference.SETTING, documentFhirQuery); + map(query.getHealthcareFacilityTypeCodes(),DocumentReference.FACILITY, documentFhirQuery); + map(query.getFormatCodes(),DocumentReference.FORMAT, documentFhirQuery); + map(query.getStatus(), documentFhirQuery); + map(query.getEventCodes(), DocumentReference.EVENT, documentFhirQuery); + map(query.getConfidentialityCodes(), DocumentReference.SECURITY_LABEL, documentFhirQuery); + map(query.getCreationTime(), DocumentReference.DATE, documentFhirQuery); + map(query.getServiceStartTime(), DocumentReference.PERIOD, documentFhirQuery); + map(query.getServiceStopTime(), DocumentReference.PERIOD, documentFhirQuery); //TODO: author } - private void mapPatientIdToQuery(PatientIdBasedStoredQuery query) { + private void mapPatientIdToQuery(PatientIdBasedStoredQuery query, IQuery fhirQuery) { var patientId = query.getPatientId(); var identifier = DocumentReference.PATIENT @@ -108,11 +110,11 @@ public void visit(GetDocumentsQuery query) { } var identifier = DocumentReference.IDENTIFIER.exactly().systemAndValues(URI_URN, searchIdentifiers.stream().map(MappingSupport::toUrnCoded).collect(Collectors.toList())); - fhirQuery.where(identifier); + documentFhirQuery.where(identifier); } - private void map(TimeRange dateRange, DateClientParam date) { + private void map(TimeRange dateRange, DateClientParam date, IQuery fhirQuery) { if (dateRange != null) { if (dateRange.getFrom() != null) { fhirQuery.where(date.afterOrEquals().millis(Date.from(dateRange.getFrom().getDateTime().toInstant()))); @@ -123,7 +125,7 @@ private void map(TimeRange dateRange, DateClientParam date) { } } - private void map(List status) { + private void map(List status, IQuery fhirQuery) { List fhirStatus = status.stream() .map(MappingSupport.STATUS_MAPPING_FROM_XDS::get) .filter(Objects::nonNull) @@ -133,12 +135,12 @@ private void map(List status) { fhirQuery.where(DocumentReference.STATUS.exactly().codes(fhirStatus)); } - private void map (QueryList codes, TokenClientParam param) { + private void map (QueryList codes, TokenClientParam param, IQuery fhirQuery) { if (codes != null) - codes.getOuterList().forEach(eventList -> map(eventList, param)); + codes.getOuterList().forEach(eventList -> map(eventList, param, fhirQuery)); } - private void map(List codes, TokenClientParam param) { + private void map(List codes, TokenClientParam param, IQuery fhirQuery) { if (codes != null && !codes.isEmpty()) { fhirQuery.where(param.exactly() .codings(codes.stream() @@ -206,25 +208,30 @@ public void visit(GetAssociationsQuery query) { public void visit(GetAllQuery query) { // TODO: Need to find another solution, since Hapi do not yet support Fhir's multi resource query // https://github.com/hapifhir/hapi-fhir/issues/685 - this.fhirQuery = client.search().forAllResources() - .withAnyProfile(Lists.newArrayList(MappingSupport.MHD_COMPREHENSIVE_SUBMISSIONSET_PROFILE, - MappingSupport.MHD_COMPREHENSIVE_PROFILE)) - .where(new TokenClientParam(Constants.PARAM_TYPE).exactly().codes("Patient","List")) + this.documentFhirQuery = client.search().forResource(DocumentReference.class) + .withProfile(MappingSupport.MHD_COMPREHENSIVE_PROFILE) .include(DocumentReference.INCLUDE_SUBJECT) + .returnBundle(Bundle.class); + this.submissionSetfhirQuery = client.search().forResource(MhdSubmissionSet.class) + .withProfile(MappingSupport.MHD_COMPREHENSIVE_SUBMISSIONSET_PROFILE) + .where(MhdSubmissionSet.CODE.exactly() + .codings(MhdSubmissionSet.SUBMISSIONSET_CODEING.getCodingFirstRep())) .include(MhdSubmissionSet.INCLUDE_SUBJECT) .returnBundle(Bundle.class); - mapPatientIdToQuery(query); + + mapPatientIdToQuery(query, documentFhirQuery); + mapPatientIdToQuery(query, submissionSetfhirQuery); } @Override public void visit(FindSubmissionSetsQuery query) { - this.fhirQuery = client.search().forResource(MhdSubmissionSet.class) + this.submissionSetfhirQuery = client.search().forResource(MhdSubmissionSet.class) .withProfile(MappingSupport.MHD_COMPREHENSIVE_SUBMISSIONSET_PROFILE) .where(MhdSubmissionSet.CODE.exactly() .codings(MhdSubmissionSet.SUBMISSIONSET_CODEING.getCodingFirstRep())) .include(MhdSubmissionSet.INCLUDE_SUBJECT) .returnBundle(Bundle.class); - mapPatientIdToQuery(query); + mapPatientIdToQuery(query, submissionSetfhirQuery); } @Override @@ -238,7 +245,7 @@ public void visit(FindDocumentsByReferenceIdQuery query) { private void mapRefId(List refId) { List searchParam = refId.stream().map(this::asSearchToken).collect(Collectors.toList()); - fhirQuery.where(DocumentReference.RELATED.hasAnyOfIds(searchParam)); + documentFhirQuery.where(DocumentReference.RELATED.hasAnyOfIds(searchParam)); } private String asSearchToken(ReferenceId id) { @@ -294,4 +301,61 @@ public void visit(FindDocumentsByTitleQuery query) { throw new UnsupportedOperationException("Not yet implemented"); } + public Iterable getDocumentsFromResult() { + if (documentFhirQuery == null) { + return () -> Collections.emptyIterator(); + } + return () -> new PagingFhirResultIterator(documentFhirQuery.execute(), DocumentReference.class); + } + + public Iterable getSubmissionSetsFrom() { + if (submissionSetfhirQuery == null) { + return () -> Collections.emptyIterator(); + } + return () -> new PagingFhirResultIterator(submissionSetfhirQuery.execute(), MhdSubmissionSet.class); + } + + public class PagingFhirResultIterator implements Iterator { + + private Bundle resultBundle; + private final Class resultTypeClass; + private int currentIteratorIndex = 0; + + public PagingFhirResultIterator(Bundle resultBundle, Class resultTypeClass) { + this.resultBundle = resultBundle; + this.resultTypeClass = resultTypeClass; + } + + @Override + public boolean hasNext() { + if (currentIteratorIndex == getResourcesFromBundle().size()) { + nextPageIfAvailable(); + } + return currentIteratorIndex < getResourcesFromBundle().size(); + } + + private void nextPageIfAvailable() { + if (resultBundle.getLink(IBaseBundle.LINK_NEXT) != null) { + resultBundle = client.loadPage().next(resultBundle).execute(); + currentIteratorIndex = 0; + } + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException("No more elements present."); + } + T result = getResourcesFromBundle().get(currentIteratorIndex); + currentIteratorIndex++; + return result; + } + + private List getResourcesFromBundle(){ + return BundleUtil.toListOfResourcesOfType(client.getFhirContext(), + resultBundle, resultTypeClass); + } + } + + } diff --git a/src/test/java/org/openehealth/app/xdstofhir/registry/XdsToFhirApplicationIT.java b/src/test/java/org/openehealth/app/xdstofhir/registry/XdsToFhirApplicationIT.java index 055e726..ded19c9 100644 --- a/src/test/java/org/openehealth/app/xdstofhir/registry/XdsToFhirApplicationIT.java +++ b/src/test/java/org/openehealth/app/xdstofhir/registry/XdsToFhirApplicationIT.java @@ -73,10 +73,10 @@ void xdsRoundTrip() throws InterruptedException { assertEquals(1, response.getSubmissionSets().size()); assertEquals(0, response.getDocumentEntries().size()); -// response = getAllFor(patientId); -// assertEquals(SUCCESS, response.getStatus()); -// assertEquals(1, response.getSubmissionSets().size()); -// assertEquals(1, response.getDocumentEntries().size()); + response = getAllFor(patientId); + assertEquals(SUCCESS, response.getStatus()); + assertEquals(1, response.getSubmissionSets().size()); + assertEquals(1, response.getDocumentEntries().size()); } diff --git a/src/test/java/org/openehealth/app/xdstofhir/registry/query/StoredQueryVistorImplTest.java b/src/test/java/org/openehealth/app/xdstofhir/registry/query/StoredQueryVistorImplTest.java index abb2f7e..578799e 100644 --- a/src/test/java/org/openehealth/app/xdstofhir/registry/query/StoredQueryVistorImplTest.java +++ b/src/test/java/org/openehealth/app/xdstofhir/registry/query/StoredQueryVistorImplTest.java @@ -47,7 +47,7 @@ void testFindDocumentsQuery (){ .withBody("{\"resourceType\":\"Bundle\",\"type\":\"searchset\"}")); var query = (FindDocumentsQuery) SampleData.createFindDocumentsQuery().getQuery(); classUnderTest.visit(query); - classUnderTest.getFhirQuery().execute(); + classUnderTest.getDocumentsFromResult().iterator(); mockServer.verify(request() .withQueryStringParameter("date", "ge19.*", "lt19.*")//skip verify the whole datetime to avoid timezone issues @@ -73,7 +73,7 @@ void testFindSubmissionSetQuery (){ .withBody("{\"resourceType\":\"Bundle\",\"type\":\"searchset\"}")); var query = (FindSubmissionSetsQuery) SampleData.createFindSubmissionSetsQuery().getQuery(); classUnderTest.visit(query); - classUnderTest.getFhirQuery().execute(); + classUnderTest.getSubmissionSetsFrom().iterator(); mockServer.verify(request() .withQueryStringParameter("patient.identifier", "urn:oid:1.2|id1") @@ -86,19 +86,28 @@ void testFindSubmissionSetQuery (){ @Test void testGetAllQuery (){ mockServer.when( - request().withPath("/")) + request().withPath("/DocumentReference")) + .respond(response().withStatusCode(200).withContentType(MediaType.APPLICATION_JSON) + .withBody("{\"resourceType\":\"Bundle\",\"type\":\"searchset\"}")); + mockServer.when( + request().withPath("/List")) .respond(response().withStatusCode(200).withContentType(MediaType.APPLICATION_JSON) .withBody("{\"resourceType\":\"Bundle\",\"type\":\"searchset\"}")); var query = (GetAllQuery) SampleData.createGetAllQuery().getQuery(); classUnderTest.visit(query); - classUnderTest.getFhirQuery().execute(); + classUnderTest.getDocumentsFromResult().iterator(); + classUnderTest.getSubmissionSetsFrom().iterator(); - mockServer.verify(request().withQueryStringParameter("patient.identifier", "urn:oid:1.2|id1") - .withQueryStringParameter("_include", "DocumentReference:subject", "List:subject") + mockServer.verify(request("/DocumentReference") + .withQueryStringParameter("patient.identifier", "urn:oid:1.2|id1") + .withQueryStringParameter("_include", "DocumentReference:subject") + .withQueryStringParameter("_profile", + "https://profiles.ihe.net/ITI/MHD/StructureDefinition/IHE.MHD.Comprehensive.DocumentReference")); + mockServer.verify(request("/List") + .withQueryStringParameter("patient.identifier", "urn:oid:1.2|id1") + .withQueryStringParameter("_include", "List:subject") .withQueryStringParameter("_profile", - "https://profiles.ihe.net/ITI/MHD/StructureDefinition/IHE.MHD.Comprehensive.SubmissionSet," - + "https://profiles.ihe.net/ITI/MHD/StructureDefinition/IHE.MHD.Comprehensive.DocumentReference") - .withQueryStringParameter("_type", "Patient,List")); + "https://profiles.ihe.net/ITI/MHD/StructureDefinition/IHE.MHD.Comprehensive.SubmissionSet")); } }