Skip to content

Commit

Permalink
#1 XDS registry conformance Tests
Browse files Browse the repository at this point in the history
* Add XDS response mapping for Submissionsets
  • Loading branch information
Thopap committed Nov 18, 2023
1 parent 43f2233 commit 96d4bc9
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.openehealth.ipf.commons.spring.map.config.CustomMappings;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
Expand Down Expand Up @@ -66,6 +67,7 @@ public CustomMappings customMapping() {
* @param fhirClient
* @return
*/
@ConditionalOnProperty(value = "fhir.server.profile.bootstrap", havingValue = "true", matchIfMissing = true)
@Bean
public SmartInitializingSingleton createProfilesIfNeeded(IGenericClient fhirClient) {
return () -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class MappingSupport {
public static String XDS_URN = "urn:ihe:xds:";
public static String URI_URN = "urn:ietf:rfc:3986";
public static final String MHD_COMPREHENSIVE_PROFILE = "https://profiles.ihe.net/ITI/MHD/StructureDefinition/IHE.MHD.Comprehensive.DocumentReference";
public static final String MHD_COMPREHENSIVE_SUBMISSIONSET_PROFILE = "https://profiles.ihe.net/ITI/MHD/StructureDefinition/IHE.MHD.Comprehensive.SubmissionSet";

public static final Map<Precision, TemporalPrecisionEnum> PRECISION_MAP_FROM_XDS = new EnumMap<>(
Map.of(Precision.DAY, TemporalPrecisionEnum.DAY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.ListResource;
import org.openehealth.app.xdstofhir.registry.common.MappingSupport;

@ResourceDef(name = "List", profile = "https://profiles.ihe.net/ITI/MHD/StructureDefinition/IHE.MHD.Comprehensive.SubmissionSet")
@ResourceDef(name = "List", profile = MappingSupport.MHD_COMPREHENSIVE_SUBMISSIONSET_PROFILE)
public class MhdSubmissionSet extends ListResource {
private static final long serialVersionUID = 6730967324453182475L;

private static final CodeableConcept SUBMISSIONSET_CODEING = new CodeableConcept(
public static final CodeableConcept SUBMISSIONSET_CODEING = new CodeableConcept(
new Coding("https://profiles.ihe.net/ITI/MHD/CodeSystem/MHDlistTypes", "submissionset",
"SubmissionSet as a FHIR List"));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
import org.openehealth.ipf.commons.ihe.xds.core.metadata.AvailabilityStatus;
import org.openehealth.ipf.commons.ihe.xds.core.metadata.LocalizedString;
import org.openehealth.ipf.commons.ihe.xds.core.metadata.SubmissionSet;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class FhirToXdsSubmissionsetMapper extends AbstractFhirToXdsMapper
implements Function<MhdSubmissionSet, SubmissionSet> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@
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.requests.QueryRegistry;
import org.openehealth.ipf.commons.ihe.xds.core.requests.query.QueryReturnType;
Expand All @@ -33,6 +36,7 @@ public class StoredQueryProcessor implements Iti18Service {
private int maxResultCount;
private final IGenericClient client;
private final Function<DocumentReference, DocumentEntry> documentMapper;
private final Function<MhdSubmissionSet, SubmissionSet> submissionMapper;
private static final Version DEFAULT_VERSION = new Version("1");

@Override
Expand All @@ -45,10 +49,12 @@ public QueryResponse processQuery(QueryRegistry query) {
var response = new QueryResponse(Status.SUCCESS);

var xdsDocuments = getDocumentsFrom(resultBundle);
var submissionSets = getSubmissionSetsFrom(resultBundle);

while (resultBundle.getLink(IBaseBundle.LINK_NEXT) != null) {
resultBundle = client.loadPage().next(resultBundle).execute();
xdsDocuments.addAll(getDocumentsFrom(resultBundle));
submissionSets.addAll(getSubmissionSetsFrom(resultBundle));
if (xdsDocuments.size() > maxResultCount) {
response.setStatus(Status.PARTIAL_SUCCESS);
response.setErrors(Collections.singletonList(new ErrorInfo(ErrorCode.TOO_MANY_RESULTS,
Expand All @@ -59,14 +65,17 @@ public QueryResponse processQuery(QueryRegistry query) {

if (query.getReturnType().equals(QueryReturnType.LEAF_CLASS)) {
response.setDocumentEntries(xdsDocuments);
response.setSubmissionSets(submissionSets);
} else {
response.setReferences(xdsDocuments.stream().map(doc -> new ObjectReference(doc.getEntryUuid()))
response.setReferences(Stream.concat(xdsDocuments.stream(), submissionSets.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);
Expand All @@ -78,6 +87,16 @@ private List<DocumentEntry> getDocumentsFrom(Bundle resultBundle) {
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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,20 @@
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 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.Patient;
import org.hl7.fhir.r4.model.codesystems.DocumentReferenceStatus;
import org.openehealth.app.xdstofhir.registry.common.MappingSupport;
import org.openehealth.app.xdstofhir.registry.common.fhir.MhdSubmissionSet;
import org.openehealth.ipf.commons.ihe.xds.core.metadata.AvailabilityStatus;
import org.openehealth.ipf.commons.ihe.xds.core.metadata.Code;
import org.openehealth.ipf.commons.ihe.xds.core.metadata.ReferenceId;
Expand Down Expand Up @@ -50,28 +53,26 @@
import org.openehealth.ipf.commons.ihe.xds.core.requests.query.GetRelatedDocumentsQuery;
import org.openehealth.ipf.commons.ihe.xds.core.requests.query.GetSubmissionSetAndContentsQuery;
import org.openehealth.ipf.commons.ihe.xds.core.requests.query.GetSubmissionSetsQuery;
import org.openehealth.ipf.commons.ihe.xds.core.requests.query.PatientIdBasedStoredQuery;
import org.openehealth.ipf.commons.ihe.xds.core.requests.query.Query.Visitor;
import org.openehealth.ipf.commons.ihe.xds.core.requests.query.QueryList;

public class StoredQueryVistorImpl implements Visitor {
@Getter
private final IQuery<Bundle> fhirQuery;
private IQuery<Bundle> fhirQuery;
private final IGenericClient client;

public StoredQueryVistorImpl(IGenericClient client) {
this.fhirQuery = client.search().forResource(DocumentReference.class)
.withProfile(MappingSupport.MHD_COMPREHENSIVE_PROFILE)
.include(DocumentReference.INCLUDE_SUBJECT)
.returnBundle(Bundle.class);
this.client = client;
}

@Override
public void visit(FindDocumentsQuery query) {
var patientId = query.getPatientId();

var identifier = DocumentReference.PATIENT
.hasChainedProperty(Patient.IDENTIFIER.exactly().systemAndIdentifier(
OID_URN + patientId.getAssigningAuthority().getUniversalId(), patientId.getId()));
fhirQuery.where(identifier);
this.fhirQuery = 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);
Expand All @@ -87,6 +88,15 @@ public void visit(FindDocumentsQuery query) {
//TODO: author
}

private void mapPatientIdToQuery(PatientIdBasedStoredQuery query) {
var patientId = query.getPatientId();

var identifier = DocumentReference.PATIENT
.hasChainedProperty(Patient.IDENTIFIER.exactly().systemAndIdentifier(
OID_URN + patientId.getAssigningAuthority().getUniversalId(), patientId.getId()));
fhirQuery.where(identifier);
}

@Override
public void visit(GetDocumentsQuery query) {
var searchIdentifiers = new ArrayList<String>();
Expand Down Expand Up @@ -194,12 +204,27 @@ public void visit(GetAssociationsQuery query) {

@Override
public void visit(GetAllQuery query) {
throw new UnsupportedOperationException("Not yet implemented");
// 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"))
.include(DocumentReference.INCLUDE_SUBJECT)
.include(MhdSubmissionSet.INCLUDE_SUBJECT)
.returnBundle(Bundle.class);
mapPatientIdToQuery(query);
}

@Override
public void visit(FindSubmissionSetsQuery query) {
throw new UnsupportedOperationException("Not yet implemented");
this.fhirQuery = 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);
}

@Override
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ xds.endpoint.iti18=xds-iti18:registry/iti18
xds.endpoint.iti42=xds-iti42:registry/iti42
xds.endpoint.iti8=xds-iti8:0.0.0.0:2575

fhir.server.profile.bootstrap=false

server.port=8081
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import org.openehealth.ipf.commons.ihe.xds.core.metadata.Identifiable;
import org.openehealth.ipf.commons.ihe.xds.core.requests.QueryRegistry;
import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindDocumentsQuery;
import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindSubmissionSetsQuery;
import org.openehealth.ipf.commons.ihe.xds.core.requests.query.GetAllQuery;
import org.openehealth.ipf.commons.ihe.xds.core.requests.query.QueryReturnType;
import org.openehealth.ipf.commons.ihe.xds.core.responses.ErrorCode;
import org.openehealth.ipf.commons.ihe.xds.core.responses.QueryResponse;
Expand Down Expand Up @@ -62,8 +64,20 @@ void xdsRoundTrip() throws InterruptedException {

var response = findDocumentFor(patientId);
assertEquals(SUCCESS, response.getStatus());
assertEquals(1, response.getDocumentEntries().size());
assertEquals(0, response.getSubmissionSets().size());


response = findSubmissionSetsFor(patientId);
assertEquals(SUCCESS, response.getStatus());
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());

assertTrue(response.getDocumentEntries().size() == 1);
}

@Test
Expand Down Expand Up @@ -95,6 +109,27 @@ private QueryResponse findDocumentFor(Identifiable patientId) {
return response;
}

private QueryResponse getAllFor(Identifiable patientId) {
var ga = new GetAllQuery();
ga.setStatusSubmissionSets(List.of(AvailabilityStatus.APPROVED));
ga.setStatusDocuments(List.of(AvailabilityStatus.APPROVED));
ga.setStatusFolders(List.of(AvailabilityStatus.APPROVED));
ga.setPatientId(patientId);
var query = new QueryRegistry(ga, QueryReturnType.LEAF_CLASS);
var response = storedQuery.processQuery(query);
return response;
}

private QueryResponse findSubmissionSetsFor(Identifiable patientId) {
var fd = new FindSubmissionSetsQuery();
fd.setStatus(List.of(AvailabilityStatus.APPROVED));
// https://hapi.fhir.org/baseR4/DocumentReference?_pretty=true&_profile=https://profiles.ihe.net/ITI/MHD/StructureDefinition/IHE.MHD.Comprehensive.DocumentReference&patient.identifier=urn:oid:1.2.40.0.10.1.4.3.1|1419180172&_include=DocumentReference:subject
fd.setPatientId(patientId);
var query = new QueryRegistry(fd, QueryReturnType.LEAF_CLASS);
var response = storedQuery.processQuery(query);
return response;
}

private void registerSampleDocForPatient(Identifiable patientId) {
var register = SampleData.createRegisterDocumentSet();
register.getDocumentEntries().forEach(doc -> doc.setPatientId(patientId));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import org.mockserver.model.MediaType;
import org.openehealth.ipf.commons.ihe.xds.core.SampleData;
import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindDocumentsQuery;
import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindSubmissionSetsQuery;
import org.openehealth.ipf.commons.ihe.xds.core.requests.query.GetAllQuery;

@ExtendWith(MockServerExtension.class)
public class StoredQueryVistorImplTest {
Expand All @@ -32,17 +34,17 @@ void beforeEachLifecyleMethod(MockServerClient mockServer) {
@BeforeEach
void initClassUnderTest() throws IOException {
var ctx = FhirContext.forR4Cached();
mockServer.when(
request().withPath("/DocumentReference"))
.respond(response().withStatusCode(200).withContentType(MediaType.APPLICATION_JSON)
.withBody("{\"resourceType\":\"Bundle\",\"type\":\"searchset\"}"));
ctx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
newRestfulGenericClient = (GenericClient) ctx.newRestfulGenericClient("http://localhost:"+mockServer.getPort()+"/");
classUnderTest = new StoredQueryVistorImpl(newRestfulGenericClient);
}

@Test
void testFindDocumentsQuery (){
mockServer.when(
request().withPath("/DocumentReference"))
.respond(response().withStatusCode(200).withContentType(MediaType.APPLICATION_JSON)
.withBody("{\"resourceType\":\"Bundle\",\"type\":\"searchset\"}"));
var query = (FindDocumentsQuery) SampleData.createFindDocumentsQuery().getQuery();
classUnderTest.visit(query);
classUnderTest.getFhirQuery().execute();
Expand All @@ -61,7 +63,42 @@ void testFindDocumentsQuery (){
.withQueryStringParameter("facility", "urn:ihe:xds:scheme5|code5,urn:ihe:xds:scheme6|code6")
.withQueryStringParameter("status", "current")
);
}

@Test
void testFindSubmissionSetQuery (){
mockServer.when(
request().withPath("/List"))
.respond(response().withStatusCode(200).withContentType(MediaType.APPLICATION_JSON)
.withBody("{\"resourceType\":\"Bundle\",\"type\":\"searchset\"}"));
var query = (FindSubmissionSetsQuery) SampleData.createFindSubmissionSetsQuery().getQuery();
classUnderTest.visit(query);
classUnderTest.getFhirQuery().execute();

mockServer.verify(request()
.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")
.withQueryStringParameter("code", "https://profiles.ihe.net/ITI/MHD/CodeSystem/MHDlistTypes|submissionset")
);
}

@Test
void testGetAllQuery (){
mockServer.when(
request().withPath("/"))
.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();

mockServer.verify(request().withQueryStringParameter("patient.identifier", "urn:oid:1.2|id1")
.withQueryStringParameter("_include", "DocumentReference:subject", "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"));
}

}

0 comments on commit 96d4bc9

Please sign in to comment.