Skip to content
This repository was archived by the owner on Oct 6, 2025. It is now read-only.

Commit c8ecb45

Browse files
committed
hapi logical identifier resolve bug workaround
hapi can't resolve logical references based on identifiers
1 parent 5df8007 commit c8ecb45

1 file changed

Lines changed: 208 additions & 17 deletions

File tree

  • codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client

codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/client/FhirClientImpl.java

Lines changed: 208 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.util.EnumSet;
1111
import java.util.List;
1212
import java.util.Objects;
13+
import java.util.Optional;
1314
import java.util.function.Function;
1415
import java.util.regex.Matcher;
1516
import java.util.regex.Pattern;
@@ -20,8 +21,18 @@
2021
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
2122
import org.hl7.fhir.r4.model.Bundle.BundleType;
2223
import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
24+
import org.hl7.fhir.r4.model.Condition;
25+
import org.hl7.fhir.r4.model.Consent;
26+
import org.hl7.fhir.r4.model.DiagnosticReport;
2327
import org.hl7.fhir.r4.model.DomainResource;
28+
import org.hl7.fhir.r4.model.IdType;
29+
import org.hl7.fhir.r4.model.Immunization;
30+
import org.hl7.fhir.r4.model.MedicationStatement;
31+
import org.hl7.fhir.r4.model.Observation;
2432
import org.hl7.fhir.r4.model.Patient;
33+
import org.hl7.fhir.r4.model.Procedure;
34+
import org.hl7.fhir.r4.model.Reference;
35+
import org.hl7.fhir.r4.model.Resource;
2536
import org.slf4j.Logger;
2637
import org.slf4j.LoggerFactory;
2738

@@ -352,13 +363,17 @@ public PseudonymList getPseudonymsWithNewData(DateWithPrecision exportFrom, Date
352363
.flatMap(this::getPatients);
353364

354365
return new PseudonymList(patients
355-
.map(p -> p.getIdentifier().stream()
356-
.filter(i -> i.hasSystem() && i.hasValue()
357-
&& ConstantsDataTransfer.NAMING_SYSTEM_NUM_CODEX_DIC_PSEUDONYM.equals(i.getSystem()))
358-
.map(i -> i.getValue()).findFirst().orElse(null))
366+
.map(p -> getPseudonym(p, ConstantsDataTransfer.NAMING_SYSTEM_NUM_CODEX_DIC_PSEUDONYM).orElse(null))
359367
.filter(p -> p != null).distinct().collect(Collectors.toList()));
360368
}
361369

370+
private Optional<String> getPseudonym(Patient p, String namingSystem)
371+
{
372+
return p.getIdentifier().stream()
373+
.filter(i -> i.hasSystem() && i.hasValue() && namingSystem.equals(i.getSystem())).map(i -> i.getValue())
374+
.findFirst();
375+
}
376+
362377
private Stream<Patient> getPatients(Bundle bundle)
363378
{
364379
Stream<Patient> patients = getPatientsFromBundle(bundle);
@@ -433,26 +448,19 @@ private Stream<DomainResource> getNewDataWithIdentifierReferenceSupport(String p
433448
private Stream<DomainResource> getNewDataWithoutIdentifierReferenceSupport(String pseudonym,
434449
DateWithPrecision exportFrom, Date exportTo)
435450
{
436-
Bundle patientBundle = (Bundle) clientFactory.getFhirStoreClient().search().forResource(Patient.class)
437-
.where(Patient.IDENTIFIER.exactly()
438-
.systemAndIdentifier(ConstantsDataTransfer.NAMING_SYSTEM_NUM_CODEX_DIC_PSEUDONYM, pseudonym))
439-
.execute();
440-
441-
if (logger.isDebugEnabled())
442-
logger.debug("Patient search-bundle result: {}",
443-
fhirContext.newJsonParser().encodeResourceToString(patientBundle));
444-
445-
if (patientBundle.getTotal() != 1 || !(patientBundle.getEntryFirstRep().getResource() instanceof Patient))
451+
Optional<Patient> localPatient = findPatientInLocalFhirStore(
452+
ConstantsDataTransfer.NAMING_SYSTEM_NUM_CODEX_DIC_PSEUDONYM, pseudonym);
453+
if (localPatient.isEmpty())
446454
{
447455
logger.warn(
448456
"Error while retrieving patient for pseudonym {}, result bundle total not 1 or first entry not patient",
449457
pseudonym);
450458
throw new RuntimeException("Error while retrieving patient for pseudonym " + pseudonym);
451459
}
452460

453-
Patient patient = (Patient) patientBundle.getEntryFirstRep().getResource();
454461

455-
Bundle searchBundle = getSearchBundleWithPatientId(patient.getIdElement().getIdPart(), exportFrom, exportTo);
462+
Bundle searchBundle = getSearchBundleWithPatientId(localPatient.get().getIdElement().getIdPart(), exportFrom,
463+
exportTo);
456464

457465
if (logger.isDebugEnabled())
458466
logger.debug("Executing Search-Bundle: {}",
@@ -464,11 +472,26 @@ private Stream<DomainResource> getNewDataWithoutIdentifierReferenceSupport(Strin
464472
if (logger.isDebugEnabled())
465473
logger.debug("Search-Bundle result: {}", fhirContext.newJsonParser().encodeResourceToString(resultBundle));
466474

467-
return Stream.concat(Stream.of(patient),
475+
return Stream.concat(Stream.of(localPatient.get()),
468476
resultBundle.getEntry().stream().filter(e -> e.hasResource() && e.getResource() instanceof Bundle)
469477
.map(e -> (Bundle) e.getResource()).flatMap(this::getDomainResources));
470478
}
471479

480+
private Optional<Patient> findPatientInLocalFhirStore(String system, String pseudonym)
481+
{
482+
Bundle patientBundle = (Bundle) clientFactory.getFhirStoreClient().search().forResource(Patient.class)
483+
.where(Patient.IDENTIFIER.exactly().systemAndIdentifier(system, pseudonym)).execute();
484+
485+
if (logger.isDebugEnabled())
486+
logger.debug("Patient search-bundle result: {}",
487+
fhirContext.newJsonParser().encodeResourceToString(patientBundle));
488+
489+
if (patientBundle.getTotal() != 1 || !(patientBundle.getEntryFirstRep().getResource() instanceof Patient))
490+
return Optional.empty();
491+
else
492+
return Optional.of((Patient) patientBundle.getEntryFirstRep().getResource());
493+
}
494+
472495
private Stream<DomainResource> getDomainResources(Bundle bundle)
473496
{
474497
Stream<DomainResource> domainResources = getDomainResourcesFromBundle(bundle);
@@ -501,7 +524,175 @@ private Stream<DomainResource> doGetDomainResources(String nextUrl, int subTotal
501524
@Override
502525
public void storeBundle(Bundle bundle)
503526
{
527+
if (logger.isDebugEnabled())
528+
logger.debug("Bundle: {}", fhirContext.newJsonParser().encodeResourceToString(bundle));
529+
530+
if (clientFactory.supportsIdentifierReferenceSearch())
531+
clientFactory.getFhirStoreClient().transaction().withBundle(bundle)
532+
.withAdditionalHeader(Constants.HEADER_PREFER, "handling=strict").execute();
533+
else
534+
storeBundleWithoutLogicalReferencesSupport(bundle);
535+
}
536+
537+
private void storeBundleWithoutLogicalReferencesSupport(Bundle bundle)
538+
{
539+
modifyBundle(bundle);
540+
504541
clientFactory.getFhirStoreClient().transaction().withBundle(bundle)
505542
.withAdditionalHeader(Constants.HEADER_PREFER, "handling=strict").execute();
506543
}
544+
545+
private void modifyBundle(Bundle bundle)
546+
{
547+
// bundle has patient
548+
// - db has patient by pseudonym -> update references, modify conditions
549+
// - db does not have patient -> remove patient condition
550+
// bundle has no patient, select from first resource by ref
551+
// - db has patient by pseudonym -> update references, modify conditions
552+
// - error
553+
554+
Optional<Patient> bundlePatient = bundle.getEntry().stream()
555+
.filter(e -> e.hasResource() && e.getResource() instanceof Patient).map(e -> (Patient) e.getResource())
556+
.findFirst();
557+
558+
if (bundlePatient.isPresent())
559+
{
560+
Optional<String> pseudonym = getPseudonym(bundlePatient.get(),
561+
ConstantsDataTransfer.NAMING_SYSTEM_NUM_CODEX_CRR_PSEUDONYM);
562+
563+
if (pseudonym.isEmpty())
564+
throw new RuntimeException("Patient has no pseudonym");
565+
566+
Optional<Patient> localPatient = findPatientInLocalFhirStore(
567+
ConstantsDataTransfer.NAMING_SYSTEM_NUM_CODEX_CRR_PSEUDONYM, pseudonym.get());
568+
if (localPatient.isPresent())
569+
{
570+
String localPatientid = localPatient.get().getIdElement().getIdPart();
571+
modifyBundleWithPatientId(bundle, pseudonym.get(), localPatientid);
572+
}
573+
else
574+
{
575+
String tempId = bundlePatient.get().getIdElement().getIdPart();
576+
modifyBundleWithTempPatientId(bundle, pseudonym.get(), tempId);
577+
}
578+
}
579+
else
580+
{
581+
Optional<String> pseudonym = bundle.getEntry().stream().filter(e -> e.hasResource())
582+
.map(e -> e.getResource()).map(this::getSubject).filter(r -> r.hasIdentifier())
583+
.map(r -> r.getIdentifier())
584+
.filter(i -> ConstantsDataTransfer.NAMING_SYSTEM_NUM_CODEX_CRR_PSEUDONYM.equals(i.getSystem()))
585+
.map(i -> i.getValue()).findFirst();
586+
587+
if (pseudonym.isEmpty())
588+
throw new RuntimeException("Patient has no pseudonym");
589+
590+
Optional<Patient> localPatient = findPatientInLocalFhirStore(
591+
ConstantsDataTransfer.NAMING_SYSTEM_NUM_CODEX_CRR_PSEUDONYM, pseudonym.get());
592+
if (localPatient.isPresent())
593+
{
594+
String localPatientid = localPatient.get().getIdElement().getIdPart();
595+
modifyBundleWithPatientId(bundle, pseudonym.get(), localPatientid);
596+
}
597+
else
598+
{
599+
logger.warn(
600+
"Bundle does not contain Patient, and Patient with pseudonym {} not found in local fhir store",
601+
pseudonym.get());
602+
throw new RuntimeException(
603+
"Bundle has no patient and local fhir store has no patient with pseudonym " + pseudonym.get());
604+
}
605+
}
606+
607+
if (logger.isDebugEnabled())
608+
logger.debug("Modified bundle: {}", fhirContext.newJsonParser().encodeResourceToString(bundle));
609+
}
610+
611+
private void modifyBundleWithTempPatientId(Bundle bundle, String pseudonym, String tempId)
612+
{
613+
bundle.getEntry().stream().filter(e -> e.hasResource() && !(e.getResource() instanceof Patient)).forEach(e ->
614+
{
615+
setSubject(e.getResource(), new Reference(tempId));
616+
modifyConditionalUpdateUrl(e, pseudonym, "");
617+
});
618+
}
619+
620+
private void modifyBundleWithPatientId(Bundle bundle, String pseudonym, String patientId)
621+
{
622+
bundle.getEntry().stream().filter(e -> e.hasResource() && !(e.getResource() instanceof Patient)).forEach(e ->
623+
{
624+
setSubject(e.getResource(), new Reference(new IdType("Patient", patientId)));
625+
modifyConditionalUpdateUrl(e, pseudonym, "&patient=Patient/" + patientId);
626+
});
627+
}
628+
629+
private void modifyConditionalUpdateUrl(BundleEntryComponent entry, String pseudonym, String replacement)
630+
{
631+
String url = entry.getRequest().getUrl();
632+
String newUrl = url.replace(
633+
"&patient:identifier=" + ConstantsDataTransfer.NAMING_SYSTEM_NUM_CODEX_CRR_PSEUDONYM + "|" + pseudonym,
634+
replacement);
635+
entry.getRequest().setUrl(newUrl);
636+
}
637+
638+
private Resource setSubject(Resource resource, Reference patientRef)
639+
{
640+
if (resource instanceof Condition)
641+
{
642+
((Condition) resource).setSubject(patientRef);
643+
return resource;
644+
}
645+
else if (resource instanceof Consent)
646+
{
647+
((Consent) resource).setPatient(patientRef);
648+
return resource;
649+
}
650+
else if (resource instanceof DiagnosticReport)
651+
{
652+
((DiagnosticReport) resource).setSubject(patientRef);
653+
return resource;
654+
}
655+
else if (resource instanceof Immunization)
656+
{
657+
((Immunization) resource).setPatient(patientRef);
658+
return resource;
659+
}
660+
else if (resource instanceof MedicationStatement)
661+
{
662+
((MedicationStatement) resource).setSubject(patientRef);
663+
return resource;
664+
}
665+
else if (resource instanceof Observation)
666+
{
667+
((Observation) resource).setSubject(patientRef);
668+
return resource;
669+
}
670+
else if (resource instanceof Procedure)
671+
{
672+
((Procedure) resource).setSubject(patientRef);
673+
return resource;
674+
}
675+
else
676+
throw new RuntimeException("Resource of type " + resource.getResourceType().name() + " not supported");
677+
}
678+
679+
private Reference getSubject(Resource resource)
680+
{
681+
if (resource instanceof Condition)
682+
return ((Condition) resource).getSubject();
683+
else if (resource instanceof Consent)
684+
return ((Consent) resource).getPatient();
685+
else if (resource instanceof DiagnosticReport)
686+
return ((DiagnosticReport) resource).getSubject();
687+
else if (resource instanceof Immunization)
688+
return ((Immunization) resource).getPatient();
689+
else if (resource instanceof MedicationStatement)
690+
return ((MedicationStatement) resource).getSubject();
691+
else if (resource instanceof Observation)
692+
return ((Observation) resource).getSubject();
693+
else if (resource instanceof Procedure)
694+
return ((Procedure) resource).getSubject();
695+
else
696+
throw new RuntimeException("Resource of type " + resource.getResourceType().name() + " not supported");
697+
}
507698
}

0 commit comments

Comments
 (0)