diff --git a/src/main/java/org/openehealth/app/xdstofhir/registry/XdsSpringContext.java b/src/main/java/org/openehealth/app/xdstofhir/registry/XdsSpringContext.java index 3a463f0..1053fe9 100644 --- a/src/main/java/org/openehealth/app/xdstofhir/registry/XdsSpringContext.java +++ b/src/main/java/org/openehealth/app/xdstofhir/registry/XdsSpringContext.java @@ -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; @@ -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 () -> { diff --git a/src/main/java/org/openehealth/app/xdstofhir/registry/common/MappingSupport.java b/src/main/java/org/openehealth/app/xdstofhir/registry/common/MappingSupport.java index 9550772..2dcc030 100644 --- a/src/main/java/org/openehealth/app/xdstofhir/registry/common/MappingSupport.java +++ b/src/main/java/org/openehealth/app/xdstofhir/registry/common/MappingSupport.java @@ -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_MAP_FROM_XDS = new EnumMap<>( Map.of(Precision.DAY, TemporalPrecisionEnum.DAY, diff --git a/src/main/java/org/openehealth/app/xdstofhir/registry/common/fhir/MhdSubmissionSet.java b/src/main/java/org/openehealth/app/xdstofhir/registry/common/fhir/MhdSubmissionSet.java index 7126cd1..7d108a5 100644 --- a/src/main/java/org/openehealth/app/xdstofhir/registry/common/fhir/MhdSubmissionSet.java +++ b/src/main/java/org/openehealth/app/xdstofhir/registry/common/fhir/MhdSubmissionSet.java @@ -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")); diff --git a/src/main/java/org/openehealth/app/xdstofhir/registry/common/mapper/FhirToXdsSubmissionsetMapper.java b/src/main/java/org/openehealth/app/xdstofhir/registry/common/mapper/FhirToXdsSubmissionsetMapper.java index cbe9a64..1062f68 100644 --- a/src/main/java/org/openehealth/app/xdstofhir/registry/common/mapper/FhirToXdsSubmissionsetMapper.java +++ b/src/main/java/org/openehealth/app/xdstofhir/registry/common/mapper/FhirToXdsSubmissionsetMapper.java @@ -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 { 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 1d4df7c..f7d2165 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 @@ -6,6 +6,7 @@ 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; @@ -13,8 +14,10 @@ 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; @@ -33,6 +36,7 @@ public class StoredQueryProcessor implements Iti18Service { private int maxResultCount; private final IGenericClient client; private final Function documentMapper; + private final Function submissionMapper; private static final Version DEFAULT_VERSION = new Version("1"); @Override @@ -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, @@ -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 getDocumentsFrom(Bundle resultBundle) { var listOfResources = BundleUtil.toListOfResourcesOfType(client.getFhirContext(), resultBundle, DocumentReference.class); @@ -78,6 +87,16 @@ private List getDocumentsFrom(Bundle resultBundle) { 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. * 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 000448b..4e429d6 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 @@ -10,10 +10,12 @@ 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; @@ -21,6 +23,7 @@ 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; @@ -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 fhirQuery; + private IQuery 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); @@ -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(); @@ -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 diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3a5f6f0..83ccbe7 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -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 \ No newline at end of file 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 c2eff49..055e726 100644 --- a/src/test/java/org/openehealth/app/xdstofhir/registry/XdsToFhirApplicationIT.java +++ b/src/test/java/org/openehealth/app/xdstofhir/registry/XdsToFhirApplicationIT.java @@ -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; @@ -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 @@ -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)); 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 e572b2d..abb2f7e 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 @@ -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 { @@ -32,10 +34,6 @@ 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); @@ -43,6 +41,10 @@ void initClassUnderTest() throws IOException { @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(); @@ -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")); } }