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
  • Loading branch information
Thopap committed Dec 2, 2023
1 parent 96d4bc9 commit 8839761
Show file tree
Hide file tree
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;
Expand All @@ -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);
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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())));
Expand All @@ -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)
Expand All @@ -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()
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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
Expand Up @@ -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());

}

Expand Down
Loading

0 comments on commit 8839761

Please sign in to comment.