diff --git a/src/main/java/org/openehealth/app/xdstofhir/registry/query/AbstractStoredQueryVisitor.java b/src/main/java/org/openehealth/app/xdstofhir/registry/query/AbstractStoredQueryVisitor.java new file mode 100644 index 0000000..df06094 --- /dev/null +++ b/src/main/java/org/openehealth/app/xdstofhir/registry/query/AbstractStoredQueryVisitor.java @@ -0,0 +1,79 @@ +package org.openehealth.app.xdstofhir.registry.query; + +import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FetchQuery; +import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindDispensesQuery; +import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindDocumentsByTitleQuery; +import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindDocumentsForMultiplePatientsQuery; +import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindFoldersForMultiplePatientsQuery; +import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindMedicationAdministrationsQuery; +import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindMedicationListQuery; +import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindMedicationTreatmentPlansQuery; +import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindPrescriptionsForDispenseQuery; +import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindPrescriptionsForValidationQuery; +import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindPrescriptionsQuery; +import org.openehealth.ipf.commons.ihe.xds.core.requests.query.Query.Visitor; + +/** + * Supportive abstract class to ignore certain StoredQueries that are not part of + * ITI-18, but requested by the Visitor interface. + */ +public abstract class AbstractStoredQueryVisitor implements Visitor { + //=========================================================================== + //Queries not part of ITI-18 and not yet implemented + //=========================================================================== + + @Override + public void visit(FindDocumentsForMultiplePatientsQuery query) { + throw new UnsupportedOperationException("ITI-51 not yet supported"); + } + + @Override + public void visit(FindFoldersForMultiplePatientsQuery query) { + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public void visit(FetchQuery query) { + throw new UnsupportedOperationException("ITI-63 not yet supported"); + } + + @Override + public void visit(FindMedicationTreatmentPlansQuery query) { + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public void visit(FindPrescriptionsQuery query) { + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public void visit(FindDispensesQuery query) { + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public void visit(FindMedicationAdministrationsQuery query) { + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public void visit(FindPrescriptionsForValidationQuery query) { + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public void visit(FindPrescriptionsForDispenseQuery query) { + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public void visit(FindMedicationListQuery query) { + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public void visit(FindDocumentsByTitleQuery query) { + throw new UnsupportedOperationException("Gematik ePA query not yet supported"); + } +} diff --git a/src/main/java/org/openehealth/app/xdstofhir/registry/query/StoredQueryMapper.java b/src/main/java/org/openehealth/app/xdstofhir/registry/query/StoredQueryMapper.java new file mode 100644 index 0000000..d1102bc --- /dev/null +++ b/src/main/java/org/openehealth/app/xdstofhir/registry/query/StoredQueryMapper.java @@ -0,0 +1,146 @@ +package org.openehealth.app.xdstofhir.registry.query; + +import static org.openehealth.app.xdstofhir.registry.common.MappingSupport.OID_URN; +import static org.openehealth.app.xdstofhir.registry.common.MappingSupport.URI_URN; +import static org.openehealth.app.xdstofhir.registry.common.MappingSupport.toUrnCoded; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import ca.uhn.fhir.rest.gclient.DateClientParam; +import ca.uhn.fhir.rest.gclient.ICriterion; +import ca.uhn.fhir.rest.gclient.IQuery; +import ca.uhn.fhir.rest.gclient.TokenClientParam; +import lombok.experimental.UtilityClass; +import org.hl7.fhir.instance.model.api.IBaseResource; +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.Identifier; +import org.hl7.fhir.r4.model.ListResource; +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.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; +import org.openehealth.ipf.commons.ihe.xds.core.metadata.TimeRange; +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.query.GetByIdQuery; +import org.openehealth.ipf.commons.ihe.xds.core.requests.query.GetFromDocumentQuery; +import org.openehealth.ipf.commons.ihe.xds.core.requests.query.PatientIdBasedStoredQuery; +import org.openehealth.ipf.commons.ihe.xds.core.requests.query.QueryList; + +@UtilityClass +public class StoredQueryMapper { + private static final Version DEFAULT_VERSION = new Version("1"); + + public static 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()))); + } + if (dateRange.getTo() != null) { + fhirQuery.where(date.before().millis(Date.from(dateRange.getTo().getDateTime().toInstant()))); + } + } + } + + public static void map(List status, IQuery fhirQuery) { + List fhirStatus = status.stream() + .map(MappingSupport.STATUS_MAPPING_FROM_XDS::get) + .filter(Objects::nonNull) + .map(DocumentReferenceStatus::toCode) + .collect(Collectors.toList()); + if (!fhirStatus.isEmpty()) + fhirQuery.where(DocumentReference.STATUS.exactly().codes(fhirStatus)); + } + + public static void map (QueryList codes, TokenClientParam param, IQuery fhirQuery) { + if (codes != null) + codes.getOuterList().forEach(eventList -> map(eventList, param, fhirQuery)); + } + + public static void map(List codes, TokenClientParam param, IQuery fhirQuery) { + if (codes != null && !codes.isEmpty()) { + fhirQuery.where(param.exactly() + .codings(codes.stream() + .map(xdsCode -> new Coding(toUrnCoded(xdsCode.getSchemeName()), xdsCode.getCode(), null)) + .collect(Collectors.toList()).toArray(new Coding[0]))); + } + } + + public static String entryUuidFrom(IBaseResource resource) { + List identifier; + if (resource instanceof DocumentReference) { + identifier = ((DocumentReference) resource).getIdentifier(); + } else if (resource instanceof ListResource) { + identifier = ((ListResource) resource).getIdentifier(); + } else { + return null; + } + return identifier.stream().filter(id -> Identifier.IdentifierUse.OFFICIAL.equals(id.getUse())).findFirst() + .orElse(identifier.stream().findFirst().orElse(new Identifier())).getValue(); + } + + + public static List urnIdentifierList(GetFromDocumentQuery query) { + List searchIdentifiers = new ArrayList(); + if (query.getUniqueId() != null) { + searchIdentifiers.add(query.getUniqueId()); + } + if (query.getUuid() != null) { + searchIdentifiers.add(query.getUuid()); + } + searchIdentifiers = searchIdentifiers.stream().map(MappingSupport::toUrnCoded).collect(Collectors.toList()); + return searchIdentifiers; + } + + public static ICriterion buildIdentifierQuery(GetByIdQuery query, TokenClientParam param) { + var searchIdentifiers = new ArrayList(); + if (query.getUniqueIds() != null) { + searchIdentifiers.addAll(query.getUniqueIds()); + } + if (query.getUuids() != null) { + searchIdentifiers.addAll(query.getUuids()); + } + var identifier = param.exactly().systemAndValues(URI_URN, + searchIdentifiers.stream().map(MappingSupport::toUrnCoded).collect(Collectors.toList())); + return identifier; + } + + + public static void mapPatientIdToQuery(PatientIdBasedStoredQuery query, IQuery fhirQuery) { + var patientId = query.getPatientId(); + + var identifier = DocumentReference.PATIENT + .hasChainedProperty(Patient.IDENTIFIER.exactly().systemAndIdentifier( + OID_URN + patientId.getAssigningAuthority().getUniversalId(), patientId.getId())); + fhirQuery.where(identifier); + } + + public static String asSearchToken(ReferenceId id) { + if (id.getAssigningAuthority() != null) { + return OID_URN + id.getAssigningAuthority().getUniversalId() + "|" + id.getId(); + } else { + return id.getId(); + } + } + + /** + * ebRIM chapter 2.5.1 requires versionInfo and lid to be set. + * + * @return consumer setting proper defaults for lid and versionInfo + */ + public static Consumer assignDefaultVersioning() { + return meta -> { + meta.setLogicalUuid(meta.getEntryUuid()); + meta.setVersion(DEFAULT_VERSION); + }; + } +} 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 1769e90..81aaf3f 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,42 +1,19 @@ package org.openehealth.app.xdstofhir.registry.query; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.UUID; -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 lombok.Getter; import lombok.RequiredArgsConstructor; -import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.DocumentReference; -import org.hl7.fhir.r4.model.DomainResource; -import org.hl7.fhir.r4.model.Identifier; -import org.hl7.fhir.r4.model.ListResource; -import org.openehealth.app.xdstofhir.registry.common.MappingSupport; import org.openehealth.app.xdstofhir.registry.common.fhir.MhdFolder; import org.openehealth.app.xdstofhir.registry.common.fhir.MhdSubmissionSet; -import org.openehealth.ipf.commons.core.URN; -import org.openehealth.ipf.commons.ihe.xds.core.metadata.Association; -import org.openehealth.ipf.commons.ihe.xds.core.metadata.AssociationLabel; -import org.openehealth.ipf.commons.ihe.xds.core.metadata.AssociationType; import org.openehealth.ipf.commons.ihe.xds.core.metadata.DocumentEntry; import org.openehealth.ipf.commons.ihe.xds.core.metadata.Folder; -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; -import org.openehealth.ipf.commons.ihe.xds.core.responses.ErrorInfo; import org.openehealth.ipf.commons.ihe.xds.core.responses.QueryResponse; -import org.openehealth.ipf.commons.ihe.xds.core.responses.Severity; -import org.openehealth.ipf.commons.ihe.xds.core.responses.Status; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -44,193 +21,32 @@ @RequiredArgsConstructor public class StoredQueryProcessor implements Iti18Service { @Value("${xds.query.max.results:1000}") + @Getter private int maxResultCount; private final IGenericClient client; private final Function documentMapper; private final Function submissionMapper; private final Function folderMapper; - private static final Version DEFAULT_VERSION = new Version("1"); @Override public QueryResponse processQuery(QueryRegistry query) { - var visitor = new StoredQueryVistorImpl(client); + var visitor = new StoredQueryVistorImpl(client, this, query.getReturnType().equals(QueryReturnType.OBJECT_REF)); query.getQuery().accept(visitor); - var response = new QueryResponse(Status.SUCCESS); - - var fhirDocuments = mapDocuments(response, visitor.getDocumentResult()); - var fhirSubmissions = mapSubmissionSets(response, visitor.getSubmissionSetResult()); - var fhirFolder = mapFolder(response, visitor.getFolderResult()); - response.getAssociations().addAll(createAssociationsFrom(fhirSubmissions, fhirDocuments)); - response.getAssociations().addAll(createAssociationsFrom(fhirSubmissions, fhirFolder)); - Collection fdDocAssoc = createAssociationsFrom(fhirFolder, fhirDocuments); - response.getAssociations().addAll(fdDocAssoc); - response.getAssociations().addAll(createAssociationsFrom(fhirSubmissions, fdDocAssoc)); - response.getAssociations().addAll(createAssociationsBetween(fhirDocuments)); - - if (query.getReturnType().equals(QueryReturnType.OBJECT_REF)) { - response.setReferences(Stream - .concat(Stream.concat(response.getDocumentEntries().stream(), - response.getSubmissionSets().stream()), response.getFolders().stream()) - .map(xdsObject -> new ObjectReference(xdsObject.getEntryUuid())).collect(Collectors.toList())); - response.getDocumentEntries().clear(); - response.getSubmissionSets().clear(); - response.getFolders().clear(); - } - - return response; - } - - private Collection createAssociationsBetween(List fhirDocuments) { - var xdsAssocations = new ArrayList(); - for (var doc : fhirDocuments) { - for (var related : doc.getRelatesTo()) { - for (var doc2 : fhirDocuments) { - if (related.getTarget().hasReference() && doc2.getId().contains(related.getTarget().getReference())) { - String assocEntryUuid = related.getId() != null ? related.getId() - : new URN(UUID.randomUUID()).toString(); - AssociationType type = MappingSupport.DOC_DOC_XDS_ASSOCIATIONS.get(related.getCode()); - Association submissionAssociation = new Association(type, - assocEntryUuid, entryUuidFrom(doc), entryUuidFrom(doc2)); - xdsAssocations.add(submissionAssociation); - } - } - } - } - return xdsAssocations; - } - - private Collection createAssociationsFrom(List fhirSubmissions, - Collection fdDocAssoc) { - var xdsAssocations = new ArrayList(); - for (var list : fhirSubmissions) { - for (var entry : list.getEntry()) { - for (var assoc : fdDocAssoc) { - if (assoc.getEntryUuid().equals(entry.getItem().getIdentifier().getValue())) { - var targetId = assoc.getEntryUuid(); - var sourceId = entryUuidFrom(list); - if (targetId != null && sourceId != null) { - String assocEntryUuid = entry.getId() != null ? entry.getId() - : new URN(UUID.randomUUID()).toString(); - Association submissionAssociation = new Association(AssociationType.HAS_MEMBER, - assocEntryUuid, sourceId, targetId); - submissionAssociation.setLabel(AssociationLabel.ORIGINAL); - xdsAssocations.add(submissionAssociation); - } - } - } - } - } - return xdsAssocations; + return visitor.getResponse(); } - private Collection createAssociationsFrom(List lists, - List fhirDocuments) { - var xdsAssocations = new ArrayList(); - for (var list : lists) { - for (var entry : list.getEntry()) { - for (var doc : fhirDocuments) { - if (entry.getItem().hasReference() && - doc.getId().contains(entry.getItem().getReference())) { - var targetId = entryUuidFrom(doc); - var sourceId = entryUuidFrom(list); - if (targetId != null && sourceId != null) { - String assocEntryUuid = entry.getId() != null ? entry.getId() - : new URN(UUID.randomUUID()).toString(); - Association submissionAssociation = new Association(AssociationType.HAS_MEMBER, - assocEntryUuid, sourceId, targetId); - submissionAssociation.setLabel(AssociationLabel.ORIGINAL); - xdsAssocations.add(submissionAssociation); - } - } - } - } - } - return xdsAssocations; + public DocumentEntry apply(DocumentReference t) { + return documentMapper.apply(t); } - private List mapFolder(QueryResponse response, Iterable fhirFolder) { - var processedFhirFolders = new ArrayList(); - for (var folder : fhirFolder) { - if (evaluateMaxCount(response)) { - break; - } - var xdsFolder = folderMapper.apply(folder); - if (xdsFolder != null) { - assignDefaultVersioning().accept(xdsFolder); - response.getFolders().add(xdsFolder); - processedFhirFolders.add(folder); - } - } - return processedFhirFolders; + public SubmissionSet apply(MhdSubmissionSet t) { + return submissionMapper.apply(t); } - private List mapSubmissionSets(QueryResponse response, Iterable fhirSubmissions) { - var processedFhirSubmissions = new ArrayList(); - for (var submissionset : fhirSubmissions) { - if (evaluateMaxCount(response)) { - break; - } - var xdsSubmission = submissionMapper.apply(submissionset); - if (xdsSubmission != null) { - assignDefaultVersioning().accept(xdsSubmission); - response.getSubmissionSets().add(xdsSubmission); - processedFhirSubmissions.add(submissionset); - } - } - return processedFhirSubmissions; + public Folder apply(MhdFolder t) { + return folderMapper.apply(t); } - private List mapDocuments(QueryResponse response, Iterable fhirDocuments) { - var processedFhirDocs = new ArrayList(); - for (var document : fhirDocuments) { - if (evaluateMaxCount(response)) { - break; - } - var xdsDoc = documentMapper.apply(document); - if (xdsDoc != null) { - assignDefaultVersioning().accept(xdsDoc); - response.getDocumentEntries().add(xdsDoc); - processedFhirDocs.add(document); - } - } - return processedFhirDocs; - } - - private String entryUuidFrom(IBaseResource resource) { - List identifier; - if (resource instanceof DocumentReference) { - identifier = ((DocumentReference) resource).getIdentifier(); - } else if (resource instanceof ListResource) { - identifier = ((ListResource) resource).getIdentifier(); - } else { - return null; - } - return identifier.stream().filter(id -> Identifier.IdentifierUse.OFFICIAL.equals(id.getUse())).findFirst() - .orElse(identifier.stream().findFirst().orElse(new Identifier())).getValue(); - } - - private boolean evaluateMaxCount(QueryResponse response) { - int currentResourceCount = response.getDocumentEntries().size() + response.getSubmissionSets().size() + response.getFolders().size(); - if (currentResourceCount > 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))); - return true; - } - return false; - } - - /** - * 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 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 28a71c9..91d025e 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 @@ -1,58 +1,49 @@ package org.openehealth.app.xdstofhir.registry.query; -import static org.openehealth.app.xdstofhir.registry.common.MappingSupport.OID_URN; import static org.openehealth.app.xdstofhir.registry.common.MappingSupport.URI_URN; -import static org.openehealth.app.xdstofhir.registry.common.MappingSupport.toUrnCoded; +import static org.openehealth.app.xdstofhir.registry.query.StoredQueryMapper.assignDefaultVersioning; +import static org.openehealth.app.xdstofhir.registry.query.StoredQueryMapper.buildIdentifierQuery; +import static org.openehealth.app.xdstofhir.registry.query.StoredQueryMapper.entryUuidFrom; +import static org.openehealth.app.xdstofhir.registry.query.StoredQueryMapper.map; +import static org.openehealth.app.xdstofhir.registry.query.StoredQueryMapper.mapPatientIdToQuery; +import static org.openehealth.app.xdstofhir.registry.query.StoredQueryMapper.urnIdentifierList; import java.util.ArrayList; +import java.util.Collection; 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.UUID; import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import ca.uhn.fhir.rest.client.api.IGenericClient; -import ca.uhn.fhir.rest.gclient.DateClientParam; -import ca.uhn.fhir.rest.gclient.ICriterion; import ca.uhn.fhir.rest.gclient.IQuery; import ca.uhn.fhir.rest.gclient.TokenClientParam; import ca.uhn.fhir.util.BundleUtil; import lombok.Getter; +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.hl7.fhir.r4.model.ListResource; import org.openehealth.app.xdstofhir.registry.common.MappingSupport; import org.openehealth.app.xdstofhir.registry.common.fhir.MhdFolder; 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; -import org.openehealth.ipf.commons.ihe.xds.core.metadata.TimeRange; -import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FetchQuery; -import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindDispensesQuery; +import org.openehealth.ipf.commons.core.URN; +import org.openehealth.ipf.commons.ihe.xds.core.metadata.Association; +import org.openehealth.ipf.commons.ihe.xds.core.metadata.AssociationLabel; +import org.openehealth.ipf.commons.ihe.xds.core.metadata.AssociationType; +import org.openehealth.ipf.commons.ihe.xds.core.metadata.ObjectReference; import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindDocumentsByReferenceIdQuery; -import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindDocumentsByTitleQuery; -import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindDocumentsForMultiplePatientsQuery; import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindDocumentsQuery; -import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindFoldersForMultiplePatientsQuery; import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindFoldersQuery; -import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindMedicationAdministrationsQuery; -import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindMedicationListQuery; -import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindMedicationTreatmentPlansQuery; -import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindPrescriptionsForDispenseQuery; -import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindPrescriptionsForValidationQuery; -import org.openehealth.ipf.commons.ihe.xds.core.requests.query.FindPrescriptionsQuery; 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.GetAssociationsQuery; -import org.openehealth.ipf.commons.ihe.xds.core.requests.query.GetByIdAndCodesQuery; -import org.openehealth.ipf.commons.ihe.xds.core.requests.query.GetByIdQuery; import org.openehealth.ipf.commons.ihe.xds.core.requests.query.GetDocumentsAndAssociationsQuery; import org.openehealth.ipf.commons.ihe.xds.core.requests.query.GetDocumentsQuery; import org.openehealth.ipf.commons.ihe.xds.core.requests.query.GetFolderAndContentsQuery; @@ -61,9 +52,11 @@ 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; +import org.openehealth.ipf.commons.ihe.xds.core.responses.ErrorCode; +import org.openehealth.ipf.commons.ihe.xds.core.responses.ErrorInfo; +import org.openehealth.ipf.commons.ihe.xds.core.responses.QueryResponse; +import org.openehealth.ipf.commons.ihe.xds.core.responses.Severity; +import org.openehealth.ipf.commons.ihe.xds.core.responses.Status; /** * Implement ITI-18 queries using IPF visitor pattern. @@ -72,132 +65,162 @@ * * */ -public class StoredQueryVistorImpl implements Visitor { +@RequiredArgsConstructor +public class StoredQueryVistorImpl extends AbstractStoredQueryVisitor { /* * Hapi currently ignore "_list" parameter, workaround here with "_has" reverse chain search * https://github.com/hapifhir/hapi-fhir/issues/3761 */ private static final String _HAS_LIST_ITEM_IDENTIFIER = "_has:List:item:identifier"; - @Getter - private Iterable documentResult; - @Getter - private Iterable submissionSetResult; - @Getter - private Iterable folderResult; + private final IGenericClient client; + private final StoredQueryProcessor queryProcessor; + private final boolean isObjectRefResult; - public StoredQueryVistorImpl (IGenericClient client) { - this.client = client; - submissionSetResult = () -> Collections.emptyIterator(); - documentResult = () -> Collections.emptyIterator(); - folderResult = () -> Collections.emptyIterator(); - } + @Getter + private final QueryResponse response = new QueryResponse(Status.SUCCESS); @Override public void visit(FindDocumentsQuery query) { IQuery documentFhirQuery = prepareQuery(query); - buildResultForDocuments(documentFhirQuery); + mapDocuments(buildResultForDocuments(documentFhirQuery)); } @Override public void visit(GetDocumentsQuery query) { - IQuery documentFhirQuery = initDocumentQuery(); + var documentFhirQuery = initDocumentQuery(); var identifier = buildIdentifierQuery(query, DocumentReference.IDENTIFIER); documentFhirQuery.where(identifier); - - buildResultForDocuments(documentFhirQuery); + mapDocuments(buildResultForDocuments(documentFhirQuery)); } @Override public void visit(FindFoldersQuery query) { - IQuery folderFhirQuery = initFolderQuery(); + var folderFhirQuery = initFolderQuery(); mapPatientIdToQuery(query, folderFhirQuery); - buildResultForFolder(folderFhirQuery); + mapFolders(buildResultForFolder(folderFhirQuery)); } @Override public void visit(GetSubmissionSetsQuery query) { - throw new UnsupportedOperationException("Not yet implemented"); + var submissionSetfhirQuery = initSubmissionSetQuery(); + var searchIdentifiers = query.getUuids().stream().map(MappingSupport::toUrnCoded).collect(Collectors.toList()); + submissionSetfhirQuery.where(MhdSubmissionSet.ITEM.hasChainedProperty( + new TokenClientParam("identifier").exactly().systemAndValues(URI_URN, searchIdentifiers))); + buildResultForSubmissionSet(submissionSetfhirQuery); + var mapSubmissionSets = mapSubmissionSets(buildResultForSubmissionSet(submissionSetfhirQuery)); + + var documentFhirQuery = initDocumentQuery(); + documentFhirQuery.where(DocumentReference.IDENTIFIER.exactly().systemAndValues(URI_URN, searchIdentifiers)); + var folderFhirQuery = initFolderQuery(); + folderFhirQuery.where(DocumentReference.IDENTIFIER.exactly().systemAndValues(URI_URN, searchIdentifiers)); + + mapAssociations(createAssociationsFrom(mapSubmissionSets, StreamSupport + .stream(buildResultForDocuments(documentFhirQuery).spliterator(), false).collect(Collectors.toList()))); + mapAssociations(createAssociationsFrom(mapSubmissionSets, StreamSupport + .stream(buildResultForFolder(folderFhirQuery).spliterator(), false).collect(Collectors.toList()))); } + + @Override public void visit(GetSubmissionSetAndContentsQuery query) { - IQuery submissionSetfhirQuery = initSubmissionSetQuery(); - IQuery documentFhirQuery = initDocumentQuery(); + var submissionSetfhirQuery = initSubmissionSetQuery(); + var documentFhirQuery = initDocumentQuery(); map(query.getFormatCodes(),DocumentReference.FORMAT, documentFhirQuery); map(query.getConfidentialityCodes(), DocumentReference.SECURITY_LABEL, documentFhirQuery); - IQuery folderFhirQuery = initFolderQuery(); - List searchIdentifiers = urnIdentifierList(query); - submissionSetfhirQuery.where(MhdFolder.IDENTIFIER.exactly().systemAndValues(URI_URN, searchIdentifiers)); + var folderFhirQuery = initFolderQuery(); + var searchIdentifiers = urnIdentifierList(query); + submissionSetfhirQuery.where(MhdSubmissionSet.IDENTIFIER.exactly().systemAndValues(URI_URN, searchIdentifiers)); var reverseSearchCriteria = Collections.singletonMap(_HAS_LIST_ITEM_IDENTIFIER, searchIdentifiers); documentFhirQuery.whereMap(reverseSearchCriteria); folderFhirQuery.whereMap(reverseSearchCriteria); - buildResultForDocuments(documentFhirQuery); - buildResultForSubmissionSet(submissionSetfhirQuery); - buildResultForFolder(folderFhirQuery); + var fhirDocuments = mapDocuments(buildResultForDocuments(documentFhirQuery)); + var fhirFolder = mapFolders(buildResultForFolder(folderFhirQuery)); + var fhirSubmissions = mapSubmissionSets(buildResultForSubmissionSet(submissionSetfhirQuery)); + mapAssociations(createAssociationsFrom(fhirSubmissions, fhirDocuments)); + mapAssociations(createAssociationsFrom(fhirSubmissions, fhirFolder)); + var fdDocAssoc = createAssociationsFrom(fhirFolder, fhirDocuments); + mapAssociations(fdDocAssoc); + mapAssociations(createAssociationsFrom(fhirSubmissions, fdDocAssoc)); } @Override public void visit(GetRelatedDocumentsQuery query) { - IQuery documentFhirQuery = initDocumentQuery(); + var documentFhirQuery = initDocumentQuery(); documentFhirQuery.include(DocumentReference.INCLUDE_RELATESTO); - String identifier = MappingSupport.toUrnCoded(Objects.requireNonNullElse(query.getUniqueId(), query.getUuid())); + var identifier = MappingSupport.toUrnCoded(Objects.requireNonNullElse(query.getUniqueId(), query.getUuid())); documentFhirQuery.where(DocumentReference.IDENTIFIER.exactly().systemAndValues(URI_URN, identifier)); documentFhirQuery.where(DocumentReference.RELATESTO.isMissing(false)); - buildResultForDocuments(documentFhirQuery); + var resultIterator = buildResultForDocuments(documentFhirQuery); List results = new ArrayList<>(); - populateDocumentTo(results); + resultIterator.iterator().forEachRemaining(results::add); documentFhirQuery = initDocumentQuery(); documentFhirQuery.include(DocumentReference.INCLUDE_RELATESTO); documentFhirQuery.where(DocumentReference.RELATESTO .hasChainedProperty(DocumentReference.IDENTIFIER.exactly().systemAndValues(URI_URN, identifier))); documentFhirQuery.where(DocumentReference.RELATESTO.isMissing(false)); - buildResultForDocuments(documentFhirQuery); - populateDocumentTo(results); - documentResult = () -> results.iterator(); + resultIterator = buildResultForDocuments(documentFhirQuery); + resultIterator.iterator().forEachRemaining(results::add); + mapDocuments(results); + mapAssociations(createAssociationsBetween(results)); } @Override public void visit(GetFoldersQuery query) { - IQuery folderFhirQuery = initFolderQuery(); + var folderFhirQuery = initFolderQuery(); var identifier = buildIdentifierQuery(query, MhdFolder.IDENTIFIER); folderFhirQuery.where(identifier); - - buildResultForFolder(folderFhirQuery); + mapFolders(buildResultForFolder(folderFhirQuery)); } @Override public void visit(GetFoldersForDocumentQuery query) { - IQuery folderFhirQuery = initFolderQuery(); - - String identifier = MappingSupport.toUrnCoded(Objects.requireNonNullElse(query.getUniqueId(), query.getUuid())); + var folderFhirQuery = initFolderQuery(); + var identifier = MappingSupport.toUrnCoded(Objects.requireNonNullElse(query.getUniqueId(), query.getUuid())); folderFhirQuery.where(MhdFolder.ITEM.hasChainedProperty(DocumentReference.IDENTIFIER.exactly().systemAndValues(URI_URN, identifier))); - - buildResultForFolder(folderFhirQuery); + mapFolders(buildResultForFolder(folderFhirQuery)); } @Override public void visit(GetFolderAndContentsQuery query) { - IQuery documentFhirQuery = initDocumentQuery(); + var documentFhirQuery = initDocumentQuery(); map(query.getFormatCodes(),DocumentReference.FORMAT, documentFhirQuery); map(query.getConfidentialityCodes(), DocumentReference.SECURITY_LABEL, documentFhirQuery); - IQuery folderFhirQuery = initFolderQuery(); - List searchIdentifiers = urnIdentifierList(query); + var folderFhirQuery = initFolderQuery(); + var searchIdentifiers = urnIdentifierList(query); folderFhirQuery.where(MhdFolder.IDENTIFIER.exactly().systemAndValues(URI_URN, searchIdentifiers)); var reverseSearchCriteria = Collections.singletonMap(_HAS_LIST_ITEM_IDENTIFIER, searchIdentifiers); documentFhirQuery.whereMap(reverseSearchCriteria); - - buildResultForDocuments(documentFhirQuery); - buildResultForFolder(folderFhirQuery); + var fhirDocuments = mapDocuments(buildResultForDocuments(documentFhirQuery)); + var fhirFolder = mapFolders(buildResultForFolder(folderFhirQuery)); + mapAssociations(createAssociationsFrom(fhirFolder, fhirDocuments)); } @Override public void visit(GetDocumentsAndAssociationsQuery query) { - throw new UnsupportedOperationException("Not yet implemented"); + var documentFhirQuery = initDocumentQuery(); + var identifier = buildIdentifierQuery(query, DocumentReference.IDENTIFIER); + documentFhirQuery.where(identifier); + var fhirDocuments = mapDocuments(buildResultForDocuments(documentFhirQuery)); + + var folderFhirQuery = initFolderQuery(); + folderFhirQuery.where(MhdFolder.ITEM.hasChainedProperty(identifier)); + List folders = new ArrayList<>(); + buildResultForFolder(folderFhirQuery).iterator().forEachRemaining(folders::add); + + var submissionSetfhirQuery = initSubmissionSetQuery(); + submissionSetfhirQuery.where(MhdSubmissionSet.ITEM.hasChainedProperty(identifier)); + List submissionSets = new ArrayList<>(); + buildResultForSubmissionSet(submissionSetfhirQuery).iterator().forEachRemaining(submissionSets::add); + + mapAssociations(createAssociationsFrom(folders, fhirDocuments)); + mapAssociations(createAssociationsFrom(submissionSets, fhirDocuments)); + mapAssociations(createAssociationsBetween(fhirDocuments)); } @Override @@ -207,66 +230,59 @@ public void visit(GetAssociationsQuery query) { @Override public void visit(GetAllQuery query) { - IQuery documentFhirQuery = initDocumentQuery(); - IQuery submissionSetfhirQuery = initSubmissionSetQuery(); - IQuery folderFhirQuery = initFolderQuery(); - + var documentFhirQuery = initDocumentQuery(); + var submissionSetfhirQuery = initSubmissionSetQuery(); + var folderFhirQuery = initFolderQuery(); mapPatientIdToQuery(query, documentFhirQuery); mapPatientIdToQuery(query, submissionSetfhirQuery); mapPatientIdToQuery(query, folderFhirQuery); - - buildResultForDocuments(documentFhirQuery); - buildResultForSubmissionSet(submissionSetfhirQuery); - buildResultForFolder(folderFhirQuery); + var fhirDocuments = mapDocuments(buildResultForDocuments(documentFhirQuery)); + var fhirSubmissions = mapSubmissionSets(buildResultForSubmissionSet(submissionSetfhirQuery)); + var fhirFolder = mapFolders(buildResultForFolder(folderFhirQuery)); + mapAssociations(createAssociationsFrom(fhirSubmissions, fhirDocuments)); + mapAssociations(createAssociationsFrom(fhirSubmissions, fhirFolder)); + var fdDocAssoc = createAssociationsFrom(fhirFolder, fhirDocuments); + mapAssociations(fdDocAssoc); + mapAssociations(createAssociationsFrom(fhirSubmissions, fdDocAssoc)); + mapAssociations(createAssociationsBetween(fhirDocuments)); } @Override public void visit(FindSubmissionSetsQuery query) { - IQuery submissionSetfhirQuery = initSubmissionSetQuery(); + var submissionSetfhirQuery = initSubmissionSetQuery(); mapPatientIdToQuery(query, submissionSetfhirQuery); - buildResultForSubmissionSet(submissionSetfhirQuery); + if (query.getSourceIds() != null && !query.getSourceIds().isEmpty()) + submissionSetfhirQuery.where(new TokenClientParam("sourceId").exactly().codes(query.getSourceIds())); + mapSubmissionSets(buildResultForSubmissionSet(submissionSetfhirQuery)); } @Override public void visit(FindDocumentsByReferenceIdQuery query) { - IQuery documentFhirQuery = prepareQuery(query); + var documentFhirQuery = prepareQuery(query); // TODO: Not yet working as expected if (query.getReferenceIds() != null){ var searchToken = query.getTypedReferenceIds().getOuterList().stream() .flatMap(List::stream) - .map(this::asSearchToken) + .map(StoredQueryMapper::asSearchToken) .collect(Collectors.toList()); if (!searchToken.isEmpty()) { documentFhirQuery.where(DocumentReference.RELATED.hasAnyOfIds(searchToken)); } } - buildResultForDocuments(documentFhirQuery); - } - - private void populateDocumentTo(List results) { - for (var result : documentResult) { - results.add(result); - } + mapDocuments(buildResultForDocuments(documentFhirQuery)); } - private void buildResultForFolder(IQuery folderFhirQuery) { - folderResult = () -> new PagingFhirResultIterator(folderFhirQuery.execute(), MhdFolder.class); - } - private String asSearchToken(ReferenceId id) { - if (id.getAssigningAuthority() != null) { - return OID_URN + id.getAssigningAuthority().getUniversalId() + "|" + id.getId(); - } else { - return id.getId(); - } + private Iterable buildResultForFolder(IQuery folderFhirQuery) { + return () -> new PagingFhirResultIterator(folderFhirQuery.execute(), MhdFolder.class); } - private void buildResultForDocuments(IQuery documentFhirQuery) { - documentResult = () -> new PagingFhirResultIterator(documentFhirQuery.execute(), DocumentReference.class); + private Iterable buildResultForDocuments(IQuery documentFhirQuery) { + return () -> new PagingFhirResultIterator(documentFhirQuery.execute(), DocumentReference.class); } - private void buildResultForSubmissionSet(IQuery submissionSetfhirQuery) { - submissionSetResult = () -> new PagingFhirResultIterator(submissionSetfhirQuery.execute(), MhdSubmissionSet.class); + private Iterable buildResultForSubmissionSet(IQuery submissionSetfhirQuery) { + return () -> new PagingFhirResultIterator(submissionSetfhirQuery.execute(), MhdSubmissionSet.class); } private IQuery prepareQuery(FindDocumentsQuery query) { @@ -287,40 +303,6 @@ private IQuery prepareQuery(FindDocumentsQuery query) { return documentFhirQuery; } - private List urnIdentifierList(GetByIdAndCodesQuery query) { - List searchIdentifiers = new ArrayList(); - if (query.getUniqueId() != null) { - searchIdentifiers.add(query.getUniqueId()); - } - if (query.getUuid() != null) { - searchIdentifiers.add(query.getUuid()); - } - searchIdentifiers = searchIdentifiers.stream().map(MappingSupport::toUrnCoded).collect(Collectors.toList()); - return searchIdentifiers; - } - - private ICriterion buildIdentifierQuery(GetByIdQuery query, TokenClientParam param) { - var searchIdentifiers = new ArrayList(); - if (query.getUniqueIds() != null) { - searchIdentifiers.addAll(query.getUniqueIds()); - } - if (query.getUuids() != null) { - searchIdentifiers.addAll(query.getUuids()); - } - var identifier = param.exactly().systemAndValues(URI_URN, - searchIdentifiers.stream().map(MappingSupport::toUrnCoded).collect(Collectors.toList())); - return identifier; - } - - - private void mapPatientIdToQuery(PatientIdBasedStoredQuery query, IQuery fhirQuery) { - var patientId = query.getPatientId(); - - var identifier = DocumentReference.PATIENT - .hasChainedProperty(Patient.IDENTIFIER.exactly().systemAndIdentifier( - OID_URN + patientId.getAssigningAuthority().getUniversalId(), patientId.getId())); - fhirQuery.where(identifier); - } private IQuery initSubmissionSetQuery() { return client.search().forResource(MhdSubmissionSet.class) @@ -348,41 +330,6 @@ private IQuery initDocumentQuery() { } - 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()))); - } - if (dateRange.getTo() != null) { - fhirQuery.where(date.before().millis(Date.from(dateRange.getTo().getDateTime().toInstant()))); - } - } - } - - private void map(List status, IQuery fhirQuery) { - List fhirStatus = status.stream() - .map(MappingSupport.STATUS_MAPPING_FROM_XDS::get) - .filter(Objects::nonNull) - .map(DocumentReferenceStatus::toCode) - .collect(Collectors.toList()); - if (!fhirStatus.isEmpty()) - fhirQuery.where(DocumentReference.STATUS.exactly().codes(fhirStatus)); - } - - private void map (QueryList codes, TokenClientParam param, IQuery fhirQuery) { - if (codes != null) - codes.getOuterList().forEach(eventList -> map(eventList, param, fhirQuery)); - } - - private void map(List codes, TokenClientParam param, IQuery fhirQuery) { - if (codes != null && !codes.isEmpty()) { - fhirQuery.where(param.exactly() - .codings(codes.stream() - .map(xdsCode -> new Coding(toUrnCoded(xdsCode.getSchemeName()), xdsCode.getCode(), null)) - .collect(Collectors.toList()).toArray(new Coding[0]))); - } - } - /** * Lazy Fhir Page Iterator. Fetches the next result page when the iterator has loaded the last element. * @@ -430,63 +377,150 @@ private List getResourcesFromBundle(){ } } - //=========================================================================== - //Queries below are not part of ITI-18 and not yet implemented - //=========================================================================== - - @Override - public void visit(FindDocumentsForMultiplePatientsQuery query) { - throw new UnsupportedOperationException("ITI-51 not yet supported"); - } - - @Override - public void visit(FindFoldersForMultiplePatientsQuery query) { - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public void visit(FetchQuery query) { - throw new UnsupportedOperationException("ITI-63 not yet supported"); - } - - @Override - public void visit(FindMedicationTreatmentPlansQuery query) { - throw new UnsupportedOperationException("Not yet supported"); + private Collection createAssociationsBetween(List fhirDocuments) { + var xdsAssocations = new ArrayList(); + for (var doc : fhirDocuments) { + for (var related : doc.getRelatesTo()) { + for (var doc2 : fhirDocuments) { + if (related.getTarget().hasReference() && doc2.getId().contains(related.getTarget().getReference())) { + var assocEntryUuid = related.getId() != null ? related.getId() + : new URN(UUID.randomUUID()).toString(); + var type = MappingSupport.DOC_DOC_XDS_ASSOCIATIONS.get(related.getCode()); + var submissionAssociation = new Association(type, + assocEntryUuid, entryUuidFrom(doc), entryUuidFrom(doc2)); + xdsAssocations.add(submissionAssociation); + } + } + } + } + return xdsAssocations; + } + + private Collection createAssociationsFrom(List fhirSubmissions, + Collection fdDocAssoc) { + var xdsAssocations = new ArrayList(); + for (var list : fhirSubmissions) { + for (var entry : list.getEntry()) { + for (var assoc : fdDocAssoc) { + if (assoc.getEntryUuid().equals(entry.getItem().getIdentifier().getValue())) { + var targetId = assoc.getEntryUuid(); + var sourceId = entryUuidFrom(list); + if (targetId != null && sourceId != null) { + var assocEntryUuid = entry.getId() != null ? entry.getId() + : new URN(UUID.randomUUID()).toString(); + var submissionAssociation = new Association(AssociationType.HAS_MEMBER, + assocEntryUuid, sourceId, targetId); + submissionAssociation.setLabel(AssociationLabel.ORIGINAL); + xdsAssocations.add(submissionAssociation); + } + } + } + } + } + return xdsAssocations; + } + + private Collection createAssociationsFrom(List lists, + List fhirResource) { + var xdsAssocations = new ArrayList(); + for (var list : lists) { + for (var entry : list.getEntry()) { + for (var doc : fhirResource) { + if (entry.getItem().hasReference() && + doc.getId().contains(entry.getItem().getReference())) { + var targetId = entryUuidFrom(doc); + var sourceId = entryUuidFrom(list); + if (targetId != null && sourceId != null) { + var assocEntryUuid = entry.getId() != null ? entry.getId() + : new URN(UUID.randomUUID()).toString(); + var submissionAssociation = new Association(AssociationType.HAS_MEMBER, + assocEntryUuid, sourceId, targetId); + submissionAssociation.setLabel(AssociationLabel.ORIGINAL); + xdsAssocations.add(submissionAssociation); + } + } + } + } + } + return xdsAssocations; } - @Override - public void visit(FindPrescriptionsQuery query) { - throw new UnsupportedOperationException("Not yet supported"); + private List mapFolders(Iterable fhirFolder) { + var processedFhirFolders = new ArrayList(); + for (var folder : fhirFolder) { + if (evaluateMaxCount(response)) { + break; + } + var xdsFolder = queryProcessor.apply(folder); + if (xdsFolder != null) { + assignDefaultVersioning().accept(xdsFolder); + if (isObjectRefResult) + response.getReferences().add(new ObjectReference(xdsFolder.getEntryUuid())); + else + response.getFolders().add(xdsFolder); + processedFhirFolders.add(folder); + } + } + return processedFhirFolders; } - @Override - public void visit(FindDispensesQuery query) { - throw new UnsupportedOperationException("Not yet supported"); + private void mapAssociations(Collection associations) { + if (isObjectRefResult) + response.getReferences().addAll(associations.stream() + .map(assoc -> new ObjectReference(assoc.getEntryUuid())).collect(Collectors.toList())); + else + response.getAssociations().addAll(associations); } - @Override - public void visit(FindMedicationAdministrationsQuery query) { - throw new UnsupportedOperationException("Not yet supported"); + private List mapSubmissionSets(Iterable fhirSubmissions) { + var processedFhirSubmissions = new ArrayList(); + for (var submissionset : fhirSubmissions) { + if (evaluateMaxCount(response)) { + break; + } + var xdsSubmission = queryProcessor.apply(submissionset); + if (xdsSubmission != null) { + assignDefaultVersioning().accept(xdsSubmission); + if (isObjectRefResult) + response.getReferences().add(new ObjectReference(xdsSubmission.getEntryUuid())); + else + response.getSubmissionSets().add(xdsSubmission); + processedFhirSubmissions.add(submissionset); + } + } + return processedFhirSubmissions; } - @Override - public void visit(FindPrescriptionsForValidationQuery query) { - throw new UnsupportedOperationException("Not yet supported"); + private List mapDocuments(Iterable fhirDocuments) { + var processedFhirDocs = new ArrayList(); + for (var document : fhirDocuments) { + if (evaluateMaxCount(response)) { + break; + } + var xdsDoc = queryProcessor.apply(document); + if (xdsDoc != null) { + assignDefaultVersioning().accept(xdsDoc); + if (isObjectRefResult) + response.getReferences().add(new ObjectReference(xdsDoc.getEntryUuid())); + else + response.getDocumentEntries().add(xdsDoc); + processedFhirDocs.add(document); + } + } + return processedFhirDocs; } - @Override - public void visit(FindPrescriptionsForDispenseQuery query) { - throw new UnsupportedOperationException("Not yet supported"); - } - @Override - public void visit(FindMedicationListQuery query) { - throw new UnsupportedOperationException("Not yet supported"); + private boolean evaluateMaxCount(QueryResponse response) { + int currentResourceCount = response.getDocumentEntries().size() + response.getSubmissionSets().size() + response.getFolders().size(); + if (currentResourceCount > queryProcessor.getMaxResultCount()) { + response.setStatus(Status.PARTIAL_SUCCESS); + response.setErrors(Collections.singletonList(new ErrorInfo(ErrorCode.TOO_MANY_RESULTS, + "Result exceed maximum of " + queryProcessor.getMaxResultCount(), Severity.WARNING, null, null))); + return true; + } + return false; } - @Override - public void visit(FindDocumentsByTitleQuery query) { - throw new UnsupportedOperationException("Gematik ePA query not yet supported"); - } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index ed879a7..8da1d01 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -20,6 +20,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=true +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/query/StoredQueryVistorImplTest.java b/src/test/java/org/openehealth/app/xdstofhir/registry/query/StoredQueryVistorImplTest.java index 012170b..7dbd4a8 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 @@ -11,6 +11,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; import org.mockserver.client.MockServerClient; import org.mockserver.junit.jupiter.MockServerExtension; import org.mockserver.model.MediaType; @@ -19,6 +20,7 @@ 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.GetDocumentsAndAssociationsQuery; import org.openehealth.ipf.commons.ihe.xds.core.requests.query.GetFoldersForDocumentQuery; import org.openehealth.ipf.commons.ihe.xds.core.requests.query.GetFoldersQuery; import org.openehealth.ipf.commons.ihe.xds.core.requests.query.GetRelatedDocumentsQuery; @@ -42,7 +44,7 @@ void initClassUnderTest() throws IOException { var ctx = FhirContext.forR4Cached(); ctx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); newRestfulGenericClient = (GenericClient) ctx.newRestfulGenericClient("http://localhost:"+mockServer.getPort()+"/"); - classUnderTest = new StoredQueryVistorImpl(newRestfulGenericClient); + classUnderTest = new StoredQueryVistorImpl(newRestfulGenericClient, Mockito.mock(StoredQueryProcessor.class), true); } @Test @@ -53,7 +55,6 @@ void testFindDocumentsQuery (){ .withBody(EMPTY_BUNDLE_RESULT)); var query = (FindDocumentsQuery) SampleData.createFindDocumentsQuery().getQuery(); classUnderTest.visit(query); - classUnderTest.getDocumentResult().iterator(); mockServer.verify(request() .withQueryStringParameter("date", "ge19.*", "lt19.*")//skip verify the whole datetime to avoid timezone issues @@ -79,7 +80,6 @@ void testFindSubmissionSetQuery (){ .withBody(EMPTY_BUNDLE_RESULT)); var query = (FindSubmissionSetsQuery) SampleData.createFindSubmissionSetsQuery().getQuery(); classUnderTest.visit(query); - classUnderTest.getSubmissionSetResult().iterator(); mockServer.verify(request() .withQueryStringParameter("patient.identifier", "urn:oid:1.2|id1") @@ -101,9 +101,6 @@ void testGetAllQuery (){ .withBody(EMPTY_BUNDLE_RESULT)); var query = (GetAllQuery) SampleData.createGetAllQuery().getQuery(); classUnderTest.visit(query); - classUnderTest.getDocumentResult().iterator(); - classUnderTest.getSubmissionSetResult().iterator(); - classUnderTest.getFolderResult().iterator(); mockServer.verify(request("/DocumentReference") .withQueryStringParameter("patient.identifier", "urn:oid:1.2|id1") @@ -134,9 +131,6 @@ void testGetSubmissionSetAndContentsQuery (){ .withBody(EMPTY_BUNDLE_RESULT)); var query = (GetSubmissionSetAndContentsQuery) SampleData.createGetSubmissionSetAndContentsQuery().getQuery(); classUnderTest.visit(query); - classUnderTest.getDocumentResult().iterator(); - classUnderTest.getSubmissionSetResult().iterator(); - classUnderTest.getFolderResult().iterator(); mockServer.verify(request("/List") .withQueryStringParameter("identifier", "urn:ietf:rfc:3986|urn:ihe:xds:12.21.34,urn:ietf:rfc:3986|urn:uuid:1.2.3.4") @@ -165,7 +159,6 @@ void testGetFoldersQuery (){ .withBody(EMPTY_BUNDLE_RESULT)); var query = (GetFoldersQuery) SampleData.createGetFoldersQuery().getQuery(); classUnderTest.visit(query); - classUnderTest.getFolderResult().iterator(); mockServer.verify(request() .withQueryStringParameter("identifier", @@ -185,7 +178,6 @@ void testGetFolderForDocuments (){ .withBody(EMPTY_BUNDLE_RESULT)); var query = (GetFoldersForDocumentQuery) SampleData.createGetFoldersForDocumentQuery().getQuery(); classUnderTest.visit(query); - classUnderTest.getFolderResult().iterator(); mockServer.verify(request() .withQueryStringParameter("item.identifier", "urn:ietf:rfc:3986|urn:ihe:xds:12.21.34") @@ -203,7 +195,6 @@ void testGetRelatedDocuments (){ .withBody(EMPTY_BUNDLE_RESULT)); var query = (GetRelatedDocumentsQuery) SampleData.createGetRelatedDocumentsQuery().getQuery(); classUnderTest.visit(query); - classUnderTest.getDocumentResult().iterator(); mockServer.verify(request() .withQueryStringParameter("identifier", "urn:ietf:rfc:3986|urn:ihe:xds:12.21.34") @@ -219,4 +210,35 @@ void testGetRelatedDocuments (){ ); } + @Test + void testGetDocumentsAndAssociations (){ + mockServer.when( + request().withPath("/DocumentReference")) + .respond(response().withStatusCode(200).withContentType(MediaType.APPLICATION_JSON) + .withBody(EMPTY_BUNDLE_RESULT)); + mockServer.when( + request().withPath("/List")) + .respond(response().withStatusCode(200).withContentType(MediaType.APPLICATION_JSON) + .withBody(EMPTY_BUNDLE_RESULT)); + var query = (GetDocumentsAndAssociationsQuery) SampleData.createGetDocumentsAndAssociationsQuery().getQuery(); + classUnderTest.visit(query); + + mockServer.verify(request() + .withQueryStringParameter("identifier", "urn:ietf:rfc:3986|urn:ihe:xds:12.21.34,urn:ietf:rfc:3986|urn:ihe:xds:43.56.89,urn:ietf:rfc:3986|urn:uuid:1.2.3.4,urn:ietf:rfc:3986|urn:uuid:2.3.4.5") + .withQueryStringParameter("_include", "DocumentReference:subject") + .withQueryStringParameter("_profile", MappingSupport.MHD_COMPREHENSIVE_PROFILE) + ); + mockServer.verify(request("/List") + .withQueryStringParameter("item.identifier", "urn:ietf:rfc:3986|urn:ihe:xds:12.21.34,urn:ietf:rfc:3986|urn:ihe:xds:43.56.89,urn:ietf:rfc:3986|urn:uuid:1.2.3.4,urn:ietf:rfc:3986|urn:uuid:2.3.4.5") + .withQueryStringParameter("_include", "List:subject") + .withQueryStringParameter("_profile", + MappingSupport.MHD_COMPREHENSIVE_SUBMISSIONSET_PROFILE)); + + mockServer.verify(request("/List") + .withQueryStringParameter("item.identifier", "urn:ietf:rfc:3986|urn:ihe:xds:12.21.34,urn:ietf:rfc:3986|urn:ihe:xds:43.56.89,urn:ietf:rfc:3986|urn:uuid:1.2.3.4,urn:ietf:rfc:3986|urn:uuid:2.3.4.5") + .withQueryStringParameter("_include", "List:subject") + .withQueryStringParameter("_profile", + MappingSupport.MHD_COMPREHENSIVE_FOLDER_PROFILE)); + } + }