Skip to content

Commit

Permalink
#1 Add some initial mapping for associations
Browse files Browse the repository at this point in the history
  • Loading branch information
Thopap committed Dec 12, 2023
1 parent 467a501 commit 69428ba
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public class XdsToFhirDocumentMapper extends AbstractXdsToFhirMapper
@Override
public DocumentReference apply(final DocumentEntry xdsDoc) {
var fhirDoc = new DocumentReference();
fhirDoc.setId(xdsDoc.getEntryUuid());
fhirDoc.getMeta().setProfile(singletonList(new CanonicalType(MHD_COMPREHENSIVE_PROFILE)));
fhirDoc.setStatus(xdsDoc.getAvailabilityStatus() == APPROVED ? DocumentReferenceStatus.CURRENT
: DocumentReferenceStatus.SUPERSEDED);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package org.openehealth.app.xdstofhir.registry.common.mapper;

import java.util.Collections;
import java.util.function.Function;
import java.util.List;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

import lombok.RequiredArgsConstructor;
import org.hl7.fhir.r4.model.Annotation;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.Reference;
import org.openehealth.app.xdstofhir.registry.common.MappingSupport;
import org.openehealth.app.xdstofhir.registry.common.fhir.MhdFolder;
import org.openehealth.ipf.commons.ihe.xds.core.metadata.Folder;
Expand All @@ -15,10 +17,11 @@
@Component
@RequiredArgsConstructor
public class XdsToFhirFolderMapper extends AbstractXdsToFhirMapper
implements Function<Folder, MhdFolder> {
implements BiFunction<Folder, List<Reference>, MhdFolder> {
@Override
public MhdFolder apply(Folder xdFolder) {
public MhdFolder apply(Folder xdFolder, List<Reference> references) {
var mhdList = new MhdFolder();
mhdList.setId(xdFolder.getEntryUuid());
mhdList.addIdentifier(fromIdentifier(xdFolder.getEntryUuid(), Identifier.IdentifierUse.OFFICIAL));
mhdList.addIdentifier(
fromIdentifier(MappingSupport.OID_URN + xdFolder.getUniqueId(), Identifier.IdentifierUse.USUAL));
Expand All @@ -32,6 +35,7 @@ public MhdFolder apply(Folder xdFolder) {
annotation.setText(xdFolder.getComments().getValue());
mhdList.setNote(Collections.singletonList(annotation));
}
references.forEach(ref -> mhdList.addEntry().setItem(ref));
return mhdList;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package org.openehealth.app.xdstofhir.registry.common.mapper;

import java.util.Collections;
import java.util.function.Function;
import java.util.List;
import java.util.function.BiFunction;

import lombok.RequiredArgsConstructor;
import org.hl7.fhir.r4.model.Annotation;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.Reference;
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.SubmissionSet;
Expand All @@ -14,10 +16,11 @@
@Component
@RequiredArgsConstructor
public class XdsToFhirSubmissionsetMapper extends AbstractXdsToFhirMapper
implements Function<SubmissionSet, MhdSubmissionSet> {
implements BiFunction<SubmissionSet, List<Reference>, MhdSubmissionSet> {
@Override
public MhdSubmissionSet apply(SubmissionSet xdsSub) {
public MhdSubmissionSet apply(SubmissionSet xdsSub, List<Reference> references) {
var mhdList = new MhdSubmissionSet();
mhdList.setId(xdsSub.getEntryUuid());
mhdList.addIdentifier(fromIdentifier(xdsSub.getEntryUuid(), Identifier.IdentifierUse.OFFICIAL));
mhdList.addIdentifier(
fromIdentifier(MappingSupport.OID_URN + xdsSub.getUniqueId(), Identifier.IdentifierUse.USUAL));
Expand All @@ -36,6 +39,7 @@ public MhdSubmissionSet apply(SubmissionSet xdsSub) {
annotation.setText(xdsSub.getComments().getValue());
mhdList.setNote(Collections.singletonList(annotation));
}
references.forEach(ref -> mhdList.addEntry().setItem(ref));
return mhdList;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
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.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.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;
Expand Down Expand Up @@ -46,69 +57,120 @@ public QueryResponse processQuery(QueryRegistry query) {

var response = new QueryResponse(Status.SUCCESS);

var fhirDocuments = visitor.getDocumentsFromResult();
var fhirSubmissions = visitor.getSubmissionSetsFrom();
var fhirFolder = visitor.getFoldersFrom();
var xdsDocuments = new ArrayList<DocumentEntry>();
var xdsSubmissionSets = new ArrayList<SubmissionSet>();
var xdsFolders = new ArrayList<Folder>();
var fhirDocuments = mapDocuments(response, visitor.getDocumentsFromResult());
var fhirSubmissions = mapSubmissionSets(response, visitor.getSubmissionSetsFrom());
var fhirFolder = mapFolder(response, visitor.getFoldersFrom());
response.getAssociations().addAll(createAssociationsFrom(fhirSubmissions, fhirDocuments));
response.getAssociations().addAll(createAssociationsFrom(fhirSubmissions, fhirFolder));
response.getAssociations().addAll(createAssociationsFrom(fhirFolder, fhirDocuments));
// TODO: SS-FD-association is missing
// TODO: Doc-Doc association is missing

int currentResourceCount = 0;
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<Association> createAssociationsFrom(List<? extends ListResource> lists,
List<? extends DomainResource> fhirDocuments) {
var xdsAssocations = new ArrayList<Association>();
for (var list : lists) {
for (var entry : list.getEntry()) {
for (var doc : fhirDocuments) {
if (doc.getId().contains(entry.getItem().getReference())) {
var targetId = entryUuidFrom(doc);
var sourceId = entryUuidFrom(list);
if (targetId != null && sourceId != null) {
Association submissionAssociation = new Association(AssociationType.HAS_MEMBER,
new URN(UUID.randomUUID()).toString(), sourceId, targetId);
submissionAssociation.setLabel(AssociationLabel.ORIGINAL);
xdsAssocations.add(submissionAssociation);
}
}
}
}
}
return xdsAssocations;
}

for (DocumentReference document : fhirDocuments) {
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)));
private List<MhdFolder> mapFolder(QueryResponse response, Iterable<MhdFolder> fhirFolder) {
var processedFhirFolders = new ArrayList<MhdFolder>();
for (var folder : fhirFolder) {
if (evaluateMaxCount(response)) {
break;
}
var xdsDoc = documentMapper.apply(document);
if (xdsDoc != null) {
assignDefaultVersioning().accept(xdsDoc);
xdsDocuments.add(xdsDoc);
currentResourceCount++;
var xdsFolder = folderMapper.apply(folder);
if (xdsFolder != null) {
assignDefaultVersioning().accept(xdsFolder);
response.getFolders().add(xdsFolder);
processedFhirFolders.add(folder);
}
}
return processedFhirFolders;
}

for (MhdSubmissionSet submissionset : fhirSubmissions) {
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)));
private List<MhdSubmissionSet> mapSubmissionSets(QueryResponse response, Iterable<MhdSubmissionSet> fhirSubmissions) {
var processedFhirSubmissions = new ArrayList<MhdSubmissionSet>();
for (var submissionset : fhirSubmissions) {
if (evaluateMaxCount(response)) {
break;
}
var xdsSubmission = submissionMapper.apply(submissionset);
if (xdsSubmission != null) {
assignDefaultVersioning().accept(xdsSubmission);
xdsSubmissionSets.add(xdsSubmission);
currentResourceCount++;
response.getSubmissionSets().add(xdsSubmission);
processedFhirSubmissions.add(submissionset);
}
}
return processedFhirSubmissions;
}

for (MhdFolder folder : fhirFolder) {
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)));
private List<DocumentReference> mapDocuments(QueryResponse response, Iterable<DocumentReference> fhirDocuments) {
var processedFhirDocs = new ArrayList<DocumentReference>();
for (var document : fhirDocuments) {
if (evaluateMaxCount(response)) {
break;
}
var xdsFolder = folderMapper.apply(folder);
if (xdsFolder != null) {
assignDefaultVersioning().accept(xdsFolder);
xdsFolders.add(xdsFolder);
currentResourceCount++;
var xdsDoc = documentMapper.apply(document);
if (xdsDoc != null) {
assignDefaultVersioning().accept(xdsDoc);
response.getDocumentEntries().add(xdsDoc);
processedFhirDocs.add(document);
}
}
return processedFhirDocs;
}

if (query.getReturnType().equals(QueryReturnType.LEAF_CLASS)) {
response.setDocumentEntries(xdsDocuments);
response.setSubmissionSets(xdsSubmissionSets);
private String entryUuidFrom(IBaseResource resource) {
List<Identifier> identifier;
if (resource instanceof DocumentReference) {
identifier = ((DocumentReference) resource).getIdentifier();
} else if (resource instanceof ListResource) {
identifier = ((ListResource) resource).getIdentifier();
} else {
response.setReferences(Stream.concat(xdsDocuments.stream(), xdsSubmissionSets.stream())
.map(xdsObject -> new ObjectReference(xdsObject.getEntryUuid())).collect(Collectors.toList()));
return null;
}
return identifier.stream().filter(id -> Identifier.IdentifierUse.OFFICIAL.equals(id.getUse())).findFirst()
.orElse(identifier.stream().findFirst().orElse(new Identifier())).getValue();
}

return response;
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;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@
import static org.openehealth.app.xdstofhir.registry.common.MappingSupport.OID_URN;
import static org.openehealth.app.xdstofhir.registry.common.MappingSupport.URI_URN;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;

import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.client.api.IGenericClient;
Expand All @@ -13,11 +19,14 @@
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.DocumentReference;
import org.hl7.fhir.r4.model.Enumerations.DocumentReferenceStatus;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Reference;
import org.openehealth.app.xdstofhir.registry.common.MappingSupport;
import org.openehealth.app.xdstofhir.registry.common.RegistryConfiguration;
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.Association;
import org.openehealth.ipf.commons.ihe.xds.core.metadata.AssociationType;
import org.openehealth.ipf.commons.ihe.xds.core.metadata.AvailabilityStatus;
import org.openehealth.ipf.commons.ihe.xds.core.metadata.DocumentEntry;
Expand All @@ -37,37 +46,64 @@
public class RegisterDocumentsProcessor implements Iti42Service {
private final IGenericClient client;
private final Function<DocumentEntry, DocumentReference> documentMapper;
private final Function<SubmissionSet, MhdSubmissionSet> submissionSetMapper;
private final Function<Folder, MhdFolder> folderMapper;
private final BiFunction<SubmissionSet, List<Reference>, MhdSubmissionSet> submissionSetMapper;
private final BiFunction<Folder, List<Reference>, MhdFolder> folderMapper;
private final RegistryConfiguration registryConfig;

@Override
public Response processRegister(RegisterDocumentSet register) {
BundleBuilder builder = new BundleBuilder(client.getFhirContext());

validateKnownRepository(register);
register.getDocumentEntries().forEach(this::assignRegistryValues);
register.getDocumentEntries().forEach(doc -> assignRegistryValues(doc, register.getAssociations()));
register.getDocumentEntries().forEach(this::assignPatientId);
register.getFolders().forEach(this::assignRegistryValues);
register.getFolders().forEach(folder -> assignRegistryValues(folder, register.getAssociations()));
register.getFolders().forEach(this::assignPatientId);
assignPatientId(register.getSubmissionSet());
assignRegistryValues(register.getSubmissionSet());
assignRegistryValues(register.getSubmissionSet(), register.getAssociations());
var builder = new BundleBuilder(client.getFhirContext());
register.getAssociations().stream().filter(assoc -> assoc.getAssociationType() == AssociationType.REPLACE)
.forEach(assoc -> builder.addTransactionUpdateEntry(replacePreviousDocument(assoc.getTargetUuid(),
register.getDocumentEntries().stream()
.filter(doc -> doc.getEntryUuid().equals(assoc.getSourceUuid())).findFirst().map(documentMapper)
.orElseThrow(() -> new XDSMetaDataException(ValidationMessage.UNRESOLVED_REFERENCE,
assoc.getSourceUuid())))));
register.getDocumentEntries().forEach(doc -> builder.addTransactionCreateEntry(documentMapper.apply(doc)));
register.getFolders().forEach(folder -> builder.addTransactionCreateEntry(folderMapper.apply(folder)));
builder.addTransactionCreateEntry(submissionSetMapper.apply(register.getSubmissionSet()));

var documentMap = register.getDocumentEntries().stream()
.collect(Collectors.toMap(DocumentEntry::getEntryUuid, Function.identity()));

var folderReferences = createReferences(register.getAssociations(), documentMap,
register.getFolders().stream().map(XDSMetaClass::getEntryUuid).collect(Collectors.toList()));
register.getFolders().forEach(folder -> builder.addTransactionCreateEntry(folderMapper.apply(folder, folderReferences)));

var submissionReferences = createReferences(register.getAssociations(), documentMap,
Collections.singletonList(register.getSubmissionSet().getEntryUuid()));
builder.addTransactionCreateEntry(submissionSetMapper.apply(register.getSubmissionSet(), submissionReferences));

// Execute the transaction
client.transaction().withBundle(builder.getBundle()).execute();

return new Response(Status.SUCCESS);
}

/**
* Build the references for the given assocations, where the sourceId is ony of sourceId and the target is one of the docs from the
* documentMap.
*
* @param associations
* @param documentMap
* @param sourceId
* @return the List of FHIR references.
*/
private List<Reference> createReferences(List<Association> associations, Map<String, DocumentEntry> documentMap,
List<String> sourceId) {
return associations.stream()
.filter(assoc -> sourceId.contains(assoc.getSourceUuid()))
.map(assoc -> documentMap.get(assoc.getTargetUuid())).filter(Objects::nonNull)
.map(document -> new Reference(
new IdType(DocumentReference.class.getSimpleName(), document.getEntryUuid())))
.collect(Collectors.toList());
}

/**
* Perform replace according to https://profiles.ihe.net/ITI/TF/Volume2/ITI-42.html#3.42.4.1.3.5
*
Expand Down Expand Up @@ -103,9 +139,14 @@ private void validateKnownRepository(RegisterDocumentSet register) {
});
}

private void assignRegistryValues(XDSMetaClass xdsObject) {
private void assignRegistryValues(XDSMetaClass xdsObject, List<Association> associations) {
if (!xdsObject.getEntryUuid().startsWith(MappingSupport.UUID_URN)) {
var previousIdentifier = xdsObject.getEntryUuid();
xdsObject.assignEntryUuid();
associations.stream().forEach(assoc -> {
assoc.setSourceUuid(assoc.getSourceUuid().replace(previousIdentifier, xdsObject.getEntryUuid()));
assoc.setTargetUuid(assoc.getTargetUuid().replace(previousIdentifier, xdsObject.getEntryUuid()));
});;
}
xdsObject.setAvailabilityStatus(AvailabilityStatus.APPROVED);
}
Expand Down
Loading

0 comments on commit 69428ba

Please sign in to comment.