diff --git a/README.md b/README.md
index 5561d2d..8ef4927 100644
--- a/README.md
+++ b/README.md
@@ -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))
diff --git a/pom.xml b/pom.xml
index 3ffe49e..39e91ab 100644
--- a/pom.xml
+++ b/pom.xml
@@ -91,6 +91,11 @@
${mockserver.version}
test
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
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 7252f3c..e298fd5 100644
--- a/src/main/java/org/openehealth/app/xdstofhir/registry/XdsSpringContext.java
+++ b/src/main/java/org/openehealth/app/xdstofhir/registry/XdsSpringContext.java
@@ -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;
@@ -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);
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
index 4956264..4a0f549 100644
--- a/src/main/java/org/openehealth/app/xdstofhir/registry/query/StoredQueryMapper.java
+++ b/src/main/java/org/openehealth/app/xdstofhir/registry/query/StoredQueryMapper.java
@@ -50,14 +50,14 @@ public static void map(TimeRange dateRange, DateClientParam date, IQuery
}
}
- public static void map(List status, IQuery fhirQuery) {
+ public static void mapStatus(List status, TokenClientParam param, IQuery fhirQuery) {
List 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 codes, TokenClientParam param, IQuery fhirQuery) {
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 5b1c0cb..0d8c93a 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
@@ -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;
@@ -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;
@@ -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));
}
@@ -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);
@@ -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
@@ -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));
@@ -224,17 +227,42 @@ public void visit(GetDocumentsAndAssociationsQuery query) {
@Override
public void visit(GetAssociationsQuery query) {
- throw new UnsupportedOperationException("Not yet implemented");
+ var xdsAssocations = new ArrayList();
+
+ 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));
@@ -242,7 +270,7 @@ public void visit(GetAllQuery query) {
mapAssociations(createAssociationsFrom(fhirSubmissions, fhirFolder));
var fdDocAssoc = createAssociationsFrom(fhirFolder, fhirDocuments);
mapAssociations(fdDocAssoc);
- mapAssociations(createAssociationsFrom(fhirSubmissions, fdDocAssoc));
+ mapAssociations(createAssociationsBetween(fhirSubmissions, fdDocAssoc));
mapAssociations(createAssociationsBetween(fhirDocuments));
}
@@ -250,6 +278,8 @@ public void visit(GetAllQuery query) {
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));
@@ -276,16 +306,80 @@ private Iterable buildResultForFolder(IQuery folderFhirQuery)
return () -> new PagingFhirResultIterator(folderFhirQuery.execute(), MhdFolder.class);
}
- private Iterable buildResultForDocuments(IQuery documentFhirQuery) {
- return () -> new PagingFhirResultIterator(documentFhirQuery.execute(),
+ private Iterable buildResultForDocuments(Bundle documentSearchResult) {
+ return () -> new PagingFhirResultIterator(documentSearchResult,
DocumentReference.class);
}
+ private Iterable buildResultForDocuments(IQuery documentFhirQuery) {
+ return buildResultForDocuments(documentFhirQuery.execute());
+ }
+
private Iterable buildResultForSubmissionSet(IQuery submissionSetfhirQuery) {
return () -> new PagingFhirResultIterator(submissionSetfhirQuery.execute(),
MhdSubmissionSet.class);
}
+ private List collectAssociationsOfSubmissionSet(
+ IQuery submissionSetfhirQuery) {
+ var xdsAssocations = new ArrayList();
+ 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 collectAssociationsOfFolders(IQuery folderFhirQuery) {
+ var xdsAssocations = new ArrayList();
+ 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 collectAssociationsOfDocument(IQuery documentFhirQuery) {
+ var xdsAssocations = new ArrayList();
+ 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 prepareQuery(FindDocumentsQuery query) {
IQuery documentFhirQuery = initDocumentQuery();
mapPatientIdToQuery(query, documentFhirQuery);
@@ -295,7 +389,7 @@ private IQuery 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);
@@ -397,7 +491,7 @@ private Collection createAssociationsBetween(List createAssociationsFrom(List fhirSubmissions,
+ private Collection createAssociationsBetween(List fhirSubmissions,
Collection fdDocAssoc) {
var xdsAssocations = new ArrayList();
for (var list : fhirSubmissions) {