Skip to content

Commit

Permalink
#2 Add support for Remove Metadata (ITI-62)
Browse files Browse the repository at this point in the history
* Some documentation improvement
* Some validation fixes
  • Loading branch information
Thopap committed Mar 29, 2024
1 parent 0bc6a64 commit c24de7b
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 12 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Target goal is a blueprint project to see how [IPF](https://github.com/oehf/ipf)
(default endpoint: http://localhost:8081/services/registry/iti42)
* [ITI-18](https://profiles.ihe.net/ITI/TF/Volume2/ITI-18.html) to query documents from the registry
(default endpoint: http://localhost:8081/services/registry/iti18)
* [ITI-62](https://profiles.ihe.net/ITI/TF/Volume2/ITI-62.html) to remove document metadata from the registry
(default endpoint: http://localhost:8081/services/registry/iti62)
* [ITI-8](https://profiles.ihe.net/ITI/TF/Volume2/ITI-8.html) to receive a patient-identity-feed and make sure the patient exists
(default endpoint: MLLP Port 2575)

Expand All @@ -41,7 +43,7 @@ mvn failsafe:integration-test -Pit-tests
```

## Run
The CI build push the container to [dockerhub](https://hub.docker.com/r/thopap/xds-registry-to-fhir). To pull the latest image an e.g. configure the public firely FHIR server, run:
The CI build push the container to [dockerhub](https://hub.docker.com/r/thopap/xds-registry-to-fhir). To pull the latest image an e.g. configure the public [firely](https://fire.ly/) FHIR server, run:

```
docker run -it -p8080:8080 registry.hub.docker.com/thopap/xds-registry-to-fhir -e FHIR_SERVER_BASE=https://server.fire.ly
Expand All @@ -58,5 +60,4 @@ The application is not yet intended as a production ready application.

* 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))
* Tests against fhir server beyond hapi
* IHE compliance test (using the [XDS Toolkit](https://github.com/usnistgov/iheos-toolkit2))
Binary file modified src/doc/xds-to-fhir-registry_integration.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,15 @@

@FunctionalInterface
public interface Iti62Service {

/**
* Perform the ITI-62 processing as described by IHE for https://profiles.ihe.net/ITI/TF/Volume2/ITI-62.html
*
* System will collect the corresponding FHIR resources and then invoke a batch update to the fhir resource,
* either deleting or partially updating (e.g. in case just a reference is removed) resource.
*
* @param metadataToRemove - the metadata remove request message.
* @return Response with Success or Failure, depending on the operation outcome.
*/
Response remove(RemoveMetadata metadataToRemove);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
import org.hl7.fhir.instance.model.api.IBaseElement;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.DocumentReference;
import org.hl7.fhir.r4.model.DocumentReference.DocumentReferenceRelatesToComponent;
import org.hl7.fhir.r4.model.DocumentReference.DocumentRelationshipType;
import org.hl7.fhir.r4.model.ListResource;
import org.hl7.fhir.r4.model.Reference;
import org.openehealth.app.xdstofhir.registry.common.MappingSupport;
import org.openehealth.app.xdstofhir.registry.common.PagingFhirResultIterator;
import org.openehealth.app.xdstofhir.registry.query.StoredQueryMapper;
Expand All @@ -40,22 +43,26 @@ public class RemoveDocumentsProcessor implements Iti62Service {
public Response remove(RemoveMetadata metadataToRemove) {
var errorInfo = new ArrayList<ErrorInfo>();
var uuidsToDelete = new ArrayList<String>(metadataToRemove.getReferences().stream().map(ObjectReference::getId).toList());
var unmodifiedUuidsToDelete = new ArrayList<String>(uuidsToDelete);
var builder = new BundleBuilder(client.getFhirContext());

var documentFhirQuery = client.search().forResource(DocumentReference.class)
var docBundleResult = client.search().forResource(DocumentReference.class)
.withProfile(MappingSupport.MHD_COMPREHENSIVE_PROFILE)
.revInclude(ListResource.INCLUDE_ITEM)
.revInclude(DocumentReference.INCLUDE_RELATESTO)
.where(DocumentReference.IDENTIFIER.exactly().systemAndValues(URI_URN,uuidsToDelete))
.returnBundle(Bundle.class);
.returnBundle(Bundle.class)
.execute();

// collect all List Resources (SubmissionSet, Folder) without duplicates (hapi domain objects do not implement equal / hashcode)
var uniqueResults = new TreeSet<ListResource>((a, b) -> Comparator.comparing(IAnyResource::getId).compare(a, b));

var docBundleResult = documentFhirQuery.execute();
var docResult = new PagingFhirResultIterator<DocumentReference>(docBundleResult, DocumentReference.class, client);
uniqueResults.addAll(BundleUtil.toListOfResourcesOfType(client.getFhirContext(), docBundleResult, ListResource.class));
docResult.forEachRemaining(doc -> {
processAssociations(doc, doc.getRelatesTo(), uuidsToDelete, builder);
addToDeleteTransaction(doc, uuidsToDelete, builder);
if (addToDeleteTransaction(doc, uuidsToDelete, builder)) {
validateNoReferencesExists(doc, docResult, unmodifiedUuidsToDelete, errorInfo);
}
});

var reverseSearchCriteria = Collections.singletonMap("item:identifier", Collections.singletonList(
Expand All @@ -70,11 +77,11 @@ public Response remove(RemoveMetadata metadataToRemove) {
.revInclude(ListResource.INCLUDE_ITEM)
.returnBundle(Bundle.class);

new PagingFhirResultIterator<ListResource>(referenceQuery.execute(),
ListResource.class, client).forEachRemaining(uniqueResults::add);
new PagingFhirResultIterator<ListResource>(referenceQuery.execute(), ListResource.class, client)
.forEachRemaining(uniqueResults::add);

new PagingFhirResultIterator<ListResource>(listQuery.execute(),
ListResource.class, client).forEachRemaining(uniqueResults::add);
new PagingFhirResultIterator<ListResource>(listQuery.execute(), ListResource.class, client)
.forEachRemaining(uniqueResults::add);

uniqueResults.forEach(ref -> {
processAssociations(ref, ref.getEntry(), uuidsToDelete, builder);
Expand Down Expand Up @@ -108,6 +115,29 @@ public Response remove(RemoveMetadata metadataToRemove) {
return response;
}

private void validateNoReferencesExists(DocumentReference doc,
PagingFhirResultIterator<DocumentReference> docResult, ArrayList<String> uuidsToDelete, List<ErrorInfo> errorInfo) {
var listOfRelations = new ArrayList<DocumentReferenceRelatesToComponent>();
docResult.forEachRemaining(aDoc -> {
var entryUuid = StoredQueryMapper.entryUuidFrom(aDoc);
if (!uuidsToDelete.contains(entryUuid) && aDoc != doc)
listOfRelations.addAll(aDoc.getRelatesTo());
});
doc.getRelatesTo().stream().filter(docRel -> docRel.getCode().equals(DocumentRelationshipType.REPLACES)).forEach(relDoc -> {
var entryUuid = StoredQueryMapper.entryUuidFrom(relDoc.getTarget().getResource());
if (!uuidsToDelete.contains(entryUuid)){
errorInfo.add(new ErrorInfo(ErrorCode.REFERENCE_EXISTS_EXCEPTION, "Some references still exists to " + entryUuid,
Severity.ERROR, null, null));
}
});
listOfRelations.stream().map(DocumentReferenceRelatesToComponent::getTarget).filter(Objects::nonNull)
.map(Reference::getResource).filter(DocumentReference.class::isInstance)
.map(DocumentReference.class::cast)
.filter(docRef -> docRef.equalsDeep(doc))
.findAny().ifPresent(res -> errorInfo.add(new ErrorInfo(ErrorCode.REFERENCE_EXISTS_EXCEPTION, "Some references still exists to " + res.getId(),
Severity.ERROR, null, null)));
}

/**
* Remove metadata will also remove associations between registry entries. This method will ensure that these
* entries are correctly removed.
Expand Down

0 comments on commit c24de7b

Please sign in to comment.