Skip to content

Commit

Permalink
#1 Fix getAll integration tests due to missing support for multi
Browse files Browse the repository at this point in the history
resource query in hapi fhir
Thopap committed Dec 2, 2023
1 parent 96d4bc9 commit 8839761
Showing 4 changed files with 158 additions and 94 deletions.
Original file line number Diff line number Diff line change
@@ -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<DocumentEntry>();
var xdsSubmissionSets = new ArrayList<SubmissionSet>();

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<DocumentEntry> 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<SubmissionSet> 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<? super DocumentEntry> assignDefaultVersioning() {
return doc -> {
doc.setLogicalUuid(doc.getEntryUuid());
doc.setVersion(DEFAULT_VERSION);
private Consumer<? super XDSMetaClass> assignDefaultVersioning() {
return meta -> {
meta.setLogicalUuid(meta.getEntryUuid());
meta.setVersion(DEFAULT_VERSION);
};
}

Original file line number Diff line number Diff line change
@@ -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<Bundle> fhirQuery;
private final IGenericClient client;
private IQuery<Bundle> documentFhirQuery;
private IQuery<Bundle> 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<Bundle> 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<Bundle> 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<AvailabilityStatus> status) {
private void map(List<AvailabilityStatus> status, IQuery<Bundle> fhirQuery) {
List<String> fhirStatus = status.stream()
.map(MappingSupport.STATUS_MAPPING_FROM_XDS::get)
.filter(Objects::nonNull)
@@ -133,12 +135,12 @@ private void map(List<AvailabilityStatus> status) {
fhirQuery.where(DocumentReference.STATUS.exactly().codes(fhirStatus));
}

private void map (QueryList<Code> codes, TokenClientParam param) {
private void map (QueryList<Code> codes, TokenClientParam param, IQuery<Bundle> fhirQuery) {
if (codes != null)
codes.getOuterList().forEach(eventList -> map(eventList, param));
codes.getOuterList().forEach(eventList -> map(eventList, param, fhirQuery));
}

private void map(List<Code> codes, TokenClientParam param) {
private void map(List<Code> codes, TokenClientParam param, IQuery<Bundle> 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<ReferenceId> refId) {
List<String> 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<DocumentReference> getDocumentsFromResult() {
if (documentFhirQuery == null) {
return () -> Collections.emptyIterator();
}
return () -> new PagingFhirResultIterator<DocumentReference>(documentFhirQuery.execute(), DocumentReference.class);
}

public Iterable<MhdSubmissionSet> getSubmissionSetsFrom() {
if (submissionSetfhirQuery == null) {
return () -> Collections.emptyIterator();
}
return () -> new PagingFhirResultIterator<MhdSubmissionSet>(submissionSetfhirQuery.execute(), MhdSubmissionSet.class);
}

public class PagingFhirResultIterator<T extends DomainResource> implements Iterator<T> {

private Bundle resultBundle;
private final Class<T> resultTypeClass;
private int currentIteratorIndex = 0;

public PagingFhirResultIterator(Bundle resultBundle, Class<T> 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<T> getResourcesFromBundle(){
return BundleUtil.toListOfResourcesOfType(client.getFhirContext(),
resultBundle, resultTypeClass);
}
}


}
Original file line number Diff line number Diff line change
@@ -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());

}

Original file line number Diff line number Diff line change
@@ -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"));
}

}

0 comments on commit 8839761

Please sign in to comment.