Skip to content

Commit

Permalink
#1 implement ITI-18 GetAssociations query
Browse files Browse the repository at this point in the history
  • Loading branch information
Thopap committed Dec 28, 2023
1 parent 681170c commit 832ba17
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 13 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ mvn clean spring-boot:run -Pboot
## Not yet implemented
The application is not yet intended as a production ready application.

* Implemented profiles are not feature complete, e.g. ITI-18 only implement a some of the xds queries.
* Security concerns are not yet covered (e.g. https, mllps, SAML, audit, ...)
* More testing
* IHE compliance test (using the [XDS Toolkit](https://github.com/usnistgov/iheos-toolkit2))
Expand Down
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@
<version>${mockserver.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
import org.apache.cxf.BusFactory;
import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.ext.logging.LoggingFeature;
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.spring.map.config.CustomMappings;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -43,6 +46,8 @@ SpringBus springBus() {
@Bean
IGenericClient fhirClient(@Value("${fhir.server.base}") String fhirServerBase) {
var ctx = FhirContext.forR4Cached();
ctx.setDefaultTypeForProfile(MappingSupport.MHD_COMPREHENSIVE_FOLDER_PROFILE, MhdFolder.class);
ctx.setDefaultTypeForProfile(MappingSupport.MHD_COMPREHENSIVE_SUBMISSIONSET_PROFILE, MhdSubmissionSet.class);
ctx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
var loggingInterceptor = new LoggingInterceptor();
loggingInterceptor.setLogRequestSummary(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@ public static void map(TimeRange dateRange, DateClientParam date, IQuery<Bundle>
}
}

public static void map(List<AvailabilityStatus> status, IQuery<Bundle> fhirQuery) {
public static void mapStatus(List<AvailabilityStatus> status, TokenClientParam param, IQuery<Bundle> fhirQuery) {
List<String> fhirStatus = status.stream()
.map(MappingSupport.STATUS_MAPPING_FROM_XDS::get)
.filter(Objects::nonNull)
.map(DocumentReferenceStatus::toCode)
.toList();
if (!fhirStatus.isEmpty())
fhirQuery.where(DocumentReference.STATUS.exactly().codes(fhirStatus));
fhirQuery.where(param.exactly().codes(fhirStatus));
}

public static void map (QueryList<Code> codes, TokenClientParam param, IQuery<Bundle> fhirQuery) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
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.mapStatus;
import static org.openehealth.app.xdstofhir.registry.query.StoredQueryMapper.urnIdentifierList;

import java.util.ArrayList;
Expand Down Expand Up @@ -70,7 +71,7 @@ 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";
private static final String HAS_LIST_ITEM_IDENTIFIER = "_has:List:item:identifier";


private final IGenericClient client;
Expand Down Expand Up @@ -100,6 +101,8 @@ public void visit(GetDocumentsQuery query) {
public void visit(FindFoldersQuery query) {
var folderFhirQuery = initFolderQuery();
mapPatientIdToQuery(query, folderFhirQuery);
map(query.getLastUpdateTime(), ListResource.DATE, folderFhirQuery);
mapStatus(query.getStatus(),ListResource.STATUS, folderFhirQuery);
mapFolders(buildResultForFolder(folderFhirQuery));
}

Expand Down Expand Up @@ -134,7 +137,7 @@ public void visit(GetSubmissionSetAndContentsQuery query) {
var folderFhirQuery = initFolderQuery();
var searchIdentifiers = urnIdentifierList(query);
submissionSetfhirQuery.where(ListResource.IDENTIFIER.exactly().systemAndValues(URI_URN, searchIdentifiers));
var reverseSearchCriteria = Collections.singletonMap(_HAS_LIST_ITEM_IDENTIFIER, searchIdentifiers);
var reverseSearchCriteria = Collections.singletonMap(HAS_LIST_ITEM_IDENTIFIER, searchIdentifiers);
documentFhirQuery.whereMap(reverseSearchCriteria);
folderFhirQuery.whereMap(reverseSearchCriteria);

Expand All @@ -145,7 +148,7 @@ public void visit(GetSubmissionSetAndContentsQuery query) {
mapAssociations(createAssociationsFrom(fhirSubmissions, fhirFolder));
var fdDocAssoc = createAssociationsFrom(fhirFolder, fhirDocuments);
mapAssociations(fdDocAssoc);
mapAssociations(createAssociationsFrom(fhirSubmissions, fdDocAssoc));
mapAssociations(createAssociationsBetween(fhirSubmissions, fdDocAssoc));
}

@Override
Expand Down Expand Up @@ -193,7 +196,7 @@ public void visit(GetFolderAndContentsQuery query) {
var folderFhirQuery = initFolderQuery();
var searchIdentifiers = urnIdentifierList(query);
folderFhirQuery.where(ListResource.IDENTIFIER.exactly().systemAndValues(URI_URN, searchIdentifiers));
var reverseSearchCriteria = Collections.singletonMap(_HAS_LIST_ITEM_IDENTIFIER, searchIdentifiers);
var reverseSearchCriteria = Collections.singletonMap(HAS_LIST_ITEM_IDENTIFIER, searchIdentifiers);
documentFhirQuery.whereMap(reverseSearchCriteria);
var fhirDocuments = mapDocuments(buildResultForDocuments(documentFhirQuery));
var fhirFolder = mapFolders(buildResultForFolder(folderFhirQuery));
Expand Down Expand Up @@ -224,32 +227,59 @@ public void visit(GetDocumentsAndAssociationsQuery query) {

@Override
public void visit(GetAssociationsQuery query) {
throw new UnsupportedOperationException("Not yet implemented");
var xdsAssocations = new ArrayList<Association>();

var documentFhirQuery = initDocumentQuery();
documentFhirQuery.include(DocumentReference.INCLUDE_RELATESTO);
documentFhirQuery.revInclude(ListResource.INCLUDE_ITEM);
documentFhirQuery.where(DocumentReference.IDENTIFIER.exactly().systemAndValues(URI_URN,
query.getUuids()));
xdsAssocations.addAll(collectAssociationsOfDocument(documentFhirQuery));

var folderFhirQuery = initFolderQuery();
folderFhirQuery.include(ListResource.INCLUDE_ITEM);
folderFhirQuery.where(ListResource.IDENTIFIER.exactly().systemAndValues(URI_URN,
query.getUuids()));
xdsAssocations.addAll(collectAssociationsOfFolders(folderFhirQuery));

var submissionSetfhirQuery = initSubmissionSetQuery();
submissionSetfhirQuery.include(ListResource.INCLUDE_ITEM);
submissionSetfhirQuery.where(ListResource.IDENTIFIER.exactly().systemAndValues(URI_URN,
query.getUuids()));
xdsAssocations.addAll(collectAssociationsOfSubmissionSet(submissionSetfhirQuery));

mapAssociations(xdsAssocations);
}


@Override
public void visit(GetAllQuery query) {
var documentFhirQuery = initDocumentQuery();
var submissionSetfhirQuery = initSubmissionSetQuery();
var folderFhirQuery = initFolderQuery();
mapPatientIdToQuery(query, documentFhirQuery);
mapStatus(query.getStatusDocuments(),DocumentReference.STATUS, documentFhirQuery);
mapPatientIdToQuery(query, submissionSetfhirQuery);
mapStatus(query.getStatusSubmissionSets(),ListResource.STATUS, submissionSetfhirQuery);
mapPatientIdToQuery(query, folderFhirQuery);
mapStatus(query.getStatusFolders(),ListResource.STATUS, 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(fhirSubmissions, fdDocAssoc));
mapAssociations(createAssociationsBetween(fhirDocuments));
}

@Override
public void visit(FindSubmissionSetsQuery query) {
var submissionSetfhirQuery = initSubmissionSetQuery();
mapPatientIdToQuery(query, submissionSetfhirQuery);
map(query.getSubmissionTime(), ListResource.DATE, submissionSetfhirQuery);
mapStatus(query.getStatus(),ListResource.STATUS, submissionSetfhirQuery);
if (query.getSourceIds() != null && !query.getSourceIds().isEmpty())
submissionSetfhirQuery.where(new TokenClientParam("sourceId").exactly().codes(query.getSourceIds()));
mapSubmissionSets(buildResultForSubmissionSet(submissionSetfhirQuery));
Expand All @@ -276,16 +306,80 @@ private Iterable<MhdFolder> buildResultForFolder(IQuery<Bundle> folderFhirQuery)
return () -> new PagingFhirResultIterator<MhdFolder>(folderFhirQuery.execute(), MhdFolder.class);
}

private Iterable<DocumentReference> buildResultForDocuments(IQuery<Bundle> documentFhirQuery) {
return () -> new PagingFhirResultIterator<DocumentReference>(documentFhirQuery.execute(),
private Iterable<DocumentReference> buildResultForDocuments(Bundle documentSearchResult) {
return () -> new PagingFhirResultIterator<DocumentReference>(documentSearchResult,
DocumentReference.class);
}

private Iterable<DocumentReference> buildResultForDocuments(IQuery<Bundle> documentFhirQuery) {
return buildResultForDocuments(documentFhirQuery.execute());
}

private Iterable<MhdSubmissionSet> buildResultForSubmissionSet(IQuery<Bundle> submissionSetfhirQuery) {
return () -> new PagingFhirResultIterator<MhdSubmissionSet>(submissionSetfhirQuery.execute(),
MhdSubmissionSet.class);
}

private List<Association> collectAssociationsOfSubmissionSet(
IQuery<Bundle> submissionSetfhirQuery) {
var xdsAssocations = new ArrayList<Association>();
var resultForSubmissionSet = buildResultForSubmissionSet(submissionSetfhirQuery);
resultForSubmissionSet.forEach(submission -> {
submission.getEntry().forEach(entry -> {
var assocEntryUuid = entry.getId() != null ? entry.getId() : new URN(UUID.randomUUID()).toString();
if (entry.getItem().getResource() != null) {
xdsAssocations.add(new Association(AssociationType.HAS_MEMBER, assocEntryUuid,
entryUuidFrom(submission), entryUuidFrom(entry.getItem().getResource())));
} else if (entry.getItem().getIdentifier() != null){
xdsAssocations.add(new Association(AssociationType.HAS_MEMBER, assocEntryUuid,
entryUuidFrom(submission),entry.getItem().getIdentifier().getValue()));
}
});
});
return xdsAssocations;
}


private List<Association> collectAssociationsOfFolders(IQuery<Bundle> folderFhirQuery) {
var xdsAssocations = new ArrayList<Association>();
var resultForFolder = buildResultForFolder(folderFhirQuery);
resultForFolder.forEach(folder -> {
folder.getEntry().forEach(entry -> {
if (entry.getItem().getResource() instanceof DocumentReference folderDoc) {
var assocEntryUuid = entry.getId() != null ? entry.getId() : new URN(UUID.randomUUID()).toString();
xdsAssocations.add(new Association(AssociationType.HAS_MEMBER, assocEntryUuid,
entryUuidFrom(folder), entryUuidFrom(folderDoc)));
}
});
});
return xdsAssocations;
}


private List<Association> collectAssociationsOfDocument(IQuery<Bundle> documentFhirQuery) {
var xdsAssocations = new ArrayList<Association>();
Bundle docResultBundle = documentFhirQuery.execute();
var resultForDocuments = buildResultForDocuments(docResultBundle);

resultForDocuments.forEach(doc -> {
var listResources = BundleUtil.toListOfResourcesOfType(client.getFhirContext(), docResultBundle, ListResource.class);
listResources.stream().filter(list -> list.getEntry().stream().anyMatch(entry -> doc.equals(entry.getItem().getResource()))).forEach(submission -> {
xdsAssocations.add(new Association(AssociationType.HAS_MEMBER, new URN(UUID.randomUUID()).toString(),
entryUuidFrom(submission), entryUuidFrom(doc)));
});
doc.getRelatesTo().forEach(related -> {
if (related.getTarget().getResource() instanceof DocumentReference relatedDoc) {
var assocEntryUuid = related.getId() != null ? related.getId()
: new URN(UUID.randomUUID()).toString();
var type = MappingSupport.DOC_DOC_XDS_ASSOCIATIONS.get(related.getCode());
xdsAssocations
.add(new Association(type, assocEntryUuid, entryUuidFrom(doc), entryUuidFrom(relatedDoc)));
}
});
});
return xdsAssocations;
}

private IQuery<Bundle> prepareQuery(FindDocumentsQuery query) {
IQuery<Bundle> documentFhirQuery = initDocumentQuery();
mapPatientIdToQuery(query, documentFhirQuery);
Expand All @@ -295,7 +389,7 @@ private IQuery<Bundle> prepareQuery(FindDocumentsQuery query) {
map(query.getPracticeSettingCodes(),DocumentReference.SETTING, documentFhirQuery);
map(query.getHealthcareFacilityTypeCodes(),DocumentReference.FACILITY, documentFhirQuery);
map(query.getFormatCodes(),DocumentReference.FORMAT, documentFhirQuery);
map(query.getStatus(), documentFhirQuery);
mapStatus(query.getStatus(),DocumentReference.STATUS, documentFhirQuery);
map(query.getEventCodes(), DocumentReference.EVENT, documentFhirQuery);
map(query.getConfidentialityCodes(), DocumentReference.SECURITY_LABEL, documentFhirQuery);
map(query.getCreationTime(), DocumentReference.DATE, documentFhirQuery);
Expand Down Expand Up @@ -397,7 +491,7 @@ private Collection<Association> createAssociationsBetween(List<DocumentReference
return xdsAssocations;
}

private Collection<Association> createAssociationsFrom(List<MhdSubmissionSet> fhirSubmissions,
private Collection<Association> createAssociationsBetween(List<MhdSubmissionSet> fhirSubmissions,
Collection<Association> fdDocAssoc) {
var xdsAssocations = new ArrayList<Association>();
for (var list : fhirSubmissions) {
Expand Down

0 comments on commit 832ba17

Please sign in to comment.