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

Commit 4baee0b

Browse files
committed
Merge remote-tracking branch
'origin/issues/74_Bad_Conditional_Update_URLs' into develop
2 parents 4155bf2 + a93e93a commit 4baee0b

21 files changed

Lines changed: 1040 additions & 67 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public class DataTransferProcessPluginDefinition implements ProcessPluginDefinit
3737
private static final Logger logger = LoggerFactory.getLogger(DataTransferProcessPluginDefinition.class);
3838

3939
public static final String VERSION = "0.5.1";
40-
public static final LocalDate DATE = LocalDate.of(2022, 6, 27);
40+
public static final LocalDate DATE = LocalDate.of(2022, 6, 25);
4141

4242
@Override
4343
public String getName()

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ protected Optional<Patient> findPatientInLocalFhirStore(String pseudonym)
171171
}
172172
catch (UnprocessableEntityException e)
173173
{
174-
logger.warn("Error while search for Patient with pseudonym {}|{}, message: {}, status: {}",
174+
logger.warn("Error while searching for Patient with pseudonym {}|{}, message: {}, status: {}",
175175
NAMING_SYSTEM_NUM_CODEX_CRR_PSEUDONYM, pseudonym, e.getMessage(), e.getStatusCode());
176176

177177
IBaseOperationOutcome outcome = e.getOperationOutcome();
@@ -183,14 +183,20 @@ protected Optional<Patient> findPatientInLocalFhirStore(String pseudonym)
183183
}
184184
catch (BaseServerResponseException e)
185185
{
186-
logger.warn("Error while search for Patient with pseudonym {}|{}, message: {}, status: {}",
186+
logger.warn("Error while searching for Patient with pseudonym {}|{}, message: {}, status: {}",
187187
NAMING_SYSTEM_NUM_CODEX_CRR_PSEUDONYM, pseudonym, e.getMessage(), e.getStatusCode());
188+
189+
IBaseOperationOutcome outcome = e.getOperationOutcome();
190+
191+
if (outcome != null && outcome instanceof OperationOutcome)
192+
outcomeLogger.logOutcome((OperationOutcome) outcome);
193+
188194
throw e;
189195
}
190196
catch (Exception e)
191197
{
192-
logger.warn("Error while search for Patient with pseudonym " + NAMING_SYSTEM_NUM_CODEX_CRR_PSEUDONYM + "|"
193-
+ pseudonym, e);
198+
logger.warn("Error while searching for Patient with pseudonym " + NAMING_SYSTEM_NUM_CODEX_CRR_PSEUDONYM
199+
+ "|" + pseudonym, e);
194200
throw e;
195201
}
196202
}
@@ -221,10 +227,10 @@ public Stream<DomainResource> getNewData(String pseudonym, DateWithPrecision exp
221227
logger.debug("Search-Bundle result: {}",
222228
geccoClient.getFhirContext().newJsonParser().encodeResourceToString(resultBundle));
223229

224-
return Stream.concat(Stream.of(localPatient.get()),
230+
return distinctById(Stream.concat(Stream.of(localPatient.get()),
225231
resultBundle.getEntry().stream().filter(BundleEntryComponent::hasResource)
226232
.map(BundleEntryComponent::getResource).filter(r -> r instanceof Bundle).map(r -> (Bundle) r)
227-
.flatMap(this::getDomainResources));
233+
.flatMap(this::getDomainResources)));
228234
}
229235

230236
private Optional<Patient> findPatientInLocalFhirStore(String system, String pseudonym)

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

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
3131
import org.hl7.fhir.r4.model.Bundle.BundleType;
3232
import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
33+
import org.hl7.fhir.r4.model.Bundle.SearchEntryMode;
3334
import org.hl7.fhir.r4.model.DomainResource;
3435
import org.hl7.fhir.r4.model.IdType;
3536
import org.hl7.fhir.r4.model.Identifier;
@@ -59,6 +60,54 @@ public abstract class AbstractFhirClient implements GeccoFhirClient
5960
private static final Logger logger = LoggerFactory.getLogger(AbstractFhirClient.class);
6061
private static final OutcomeLogger outcomeLogger = new OutcomeLogger(logger);
6162

63+
private static final class DomainResourceUniqueByUnqualifiedVersionlessId
64+
{
65+
private final DomainResource resource;
66+
private final String unqualifiedVersionlessIdValue;
67+
68+
public DomainResourceUniqueByUnqualifiedVersionlessId(DomainResource resource)
69+
{
70+
this.resource = resource;
71+
72+
unqualifiedVersionlessIdValue = resource.getIdElement().toUnqualifiedVersionless().getValue();
73+
}
74+
75+
public DomainResource getResource()
76+
{
77+
return resource;
78+
}
79+
80+
@Override
81+
public int hashCode()
82+
{
83+
final int prime = 31;
84+
int result = 1;
85+
result = prime * result
86+
+ ((unqualifiedVersionlessIdValue == null) ? 0 : unqualifiedVersionlessIdValue.hashCode());
87+
return result;
88+
}
89+
90+
@Override
91+
public boolean equals(Object obj)
92+
{
93+
if (this == obj)
94+
return true;
95+
if (obj == null)
96+
return false;
97+
if (getClass() != obj.getClass())
98+
return false;
99+
DomainResourceUniqueByUnqualifiedVersionlessId other = (DomainResourceUniqueByUnqualifiedVersionlessId) obj;
100+
if (unqualifiedVersionlessIdValue == null)
101+
{
102+
if (other.unqualifiedVersionlessIdValue != null)
103+
return false;
104+
}
105+
else if (!unqualifiedVersionlessIdValue.equals(other.unqualifiedVersionlessIdValue))
106+
return false;
107+
return true;
108+
}
109+
}
110+
62111
private static final List<String> RESOURCES_WITH_PATIENT_REF = Arrays.asList("AllergyIntolerance", "CarePlan",
63112
"CareTeam", "ClinicalImpression", "Composition", "Condition", "Consent", "DetectedIssue", "DeviceRequest",
64113
"DeviceUseStatement", "DiagnosticReport", "DocumentManifest", "DocumentReference", "Encounter",
@@ -539,9 +588,16 @@ protected Stream<DomainResource> getDomainResources(Bundle bundle)
539588

540589
private Stream<DomainResource> getDomainResourcesFromBundle(Bundle bundle)
541590
{
542-
return bundle.getEntry().stream().filter(BundleEntryComponent::hasResource)
543-
.map(BundleEntryComponent::getResource).filter(r -> r instanceof DomainResource)
544-
.map(r -> (DomainResource) r);
591+
// includes first
592+
return Stream.concat(
593+
bundle.getEntry().stream().filter(BundleEntryComponent::hasResource)
594+
.filter(e -> e.hasSearch() && SearchEntryMode.INCLUDE.equals(e.getSearch().getMode()))
595+
.map(BundleEntryComponent::getResource).filter(r -> r instanceof DomainResource)
596+
.map(r -> (DomainResource) r),
597+
bundle.getEntry().stream().filter(BundleEntryComponent::hasResource)
598+
.filter(e -> e.hasSearch() && SearchEntryMode.MATCH.equals(e.getSearch().getMode()))
599+
.map(BundleEntryComponent::getResource).filter(r -> r instanceof DomainResource)
600+
.map(r -> (DomainResource) r));
545601
}
546602

547603
private Stream<DomainResource> doGetDomainResources(String nextUrl, int subTotal)
@@ -624,4 +680,10 @@ public void updatePatient(Patient patient)
624680
throw e;
625681
}
626682
}
683+
684+
protected Stream<DomainResource> distinctById(Stream<DomainResource> resources)
685+
{
686+
return resources.map(DomainResourceUniqueByUnqualifiedVersionlessId::new).distinct()
687+
.map(DomainResourceUniqueByUnqualifiedVersionlessId::getResource);
688+
}
627689
}

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

Lines changed: 89 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,26 @@
22

33
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.NAMING_SYSTEM_NUM_CODEX_CRR_PSEUDONYM;
44

5+
import java.util.HashMap;
6+
import java.util.List;
7+
import java.util.Map;
58
import java.util.Optional;
69
import java.util.function.Predicate;
710

811
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
912
import org.hl7.fhir.r4.model.Bundle;
1013
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
1114
import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
15+
import org.hl7.fhir.r4.model.CanonicalType;
16+
import org.hl7.fhir.r4.model.IdType;
17+
import org.hl7.fhir.r4.model.Observation;
1218
import org.hl7.fhir.r4.model.OperationOutcome;
1319
import org.hl7.fhir.r4.model.Patient;
20+
import org.hl7.fhir.r4.model.Reference;
1421
import org.hl7.fhir.r4.model.Resource;
1522
import org.slf4j.Logger;
1623
import org.slf4j.LoggerFactory;
24+
import org.springframework.web.util.UriComponentsBuilder;
1725

1826
import ca.uhn.fhir.model.api.annotation.ResourceDef;
1927
import ca.uhn.fhir.rest.api.MethodOutcome;
@@ -28,6 +36,8 @@ public class FhirBridgeClient extends AbstractComplexFhirClient
2836
private static final Logger logger = LoggerFactory.getLogger(FhirBridgeClient.class);
2937
private static final OutcomeLogger outcomeLogger = new OutcomeLogger(logger);
3038

39+
private static final String NUM_CODEX_BLOOD_GAS_PANEL = "https://www.netzwerk-universitaetsmedizin.de/fhir/StructureDefinition/blood-gas-panel";
40+
3141
/**
3242
* @param geccoClient
3343
* not <code>null</code>
@@ -43,14 +53,16 @@ public void storeBundle(Bundle bundle)
4353
// either bundle has a patient, or patient should already exists
4454
Patient patient = createOrUpdatePatient(bundle).orElseGet(() -> getExistingPatientOrThrow(bundle));
4555

56+
Map<String, IdType> resourceIdsByUuid = new HashMap<>();
4657
for (int i = 0; i < bundle.getEntry().size(); i++)
4758
{
4859
BundleEntryComponent entry = bundle.getEntry().get(i);
4960

5061
if (isEntrySupported(entry, e -> !(e.getResource() instanceof Patient)))
51-
createOrUpdateEntry(i, entry, patient);
62+
createOrUpdateEntry(i, entry, patient, resourceIdsByUuid);
63+
64+
// only log for non Patients
5265
else if (!entry.hasResource() || !(entry.getResource() instanceof Patient))
53-
// only log for non Patients
5466
logger.warn("Bundle entry at index {} not supported, ignoring entry", i);
5567
}
5668
}
@@ -220,21 +232,28 @@ private Optional<Patient> create(Patient newPatient, String pseudonym, String bu
220232
}
221233
}
222234

223-
private void createOrUpdateEntry(int index, BundleEntryComponent entry, Patient patient)
235+
private void createOrUpdateEntry(int index, BundleEntryComponent entry, Patient patient,
236+
Map<String, IdType> resourceIdsByUuid)
224237
{
225238
Resource resource = entry.getResource();
226239
String url = entry.getRequest().getUrl();
227240

228241
Optional<Resource> existingResource = findResourceInLocalFhirStore(url, resource.getClass());
229-
existingResource.ifPresentOrElse(existing -> update(existing, resource, entry.getFullUrl()),
230-
() -> create(resource, entry.getFullUrl()));
242+
IdType resourceId = existingResource.map(
243+
existing -> update(existing, fixTemporaryReferences(resource, resourceIdsByUuid), entry.getFullUrl()))
244+
.orElseGet(() -> create(fixTemporaryReferences(resource, resourceIdsByUuid), entry.getFullUrl()));
245+
246+
resourceIdsByUuid.put(entry.getFullUrl(), resourceId);
231247
}
232248

233249
private Optional<Resource> findResourceInLocalFhirStore(String url, Class<? extends Resource> resourceType)
234250
{
235251
if (geccoClient.shouldUseChainedParameterNotLogicalReference())
236252
url = url.replace("patient:identifier", "patient.identifier");
237253

254+
UriComponentsBuilder urlBuilder = UriComponentsBuilder.fromUriString(url);
255+
url = urlBuilder.encode().build().toString();
256+
238257
try
239258
{
240259
Bundle resultBundle = geccoClient.getGenericFhirClient().search().byUrl(url).sort()
@@ -254,7 +273,7 @@ private Optional<Resource> findResourceInLocalFhirStore(String url, Class<? exte
254273
return Optional.of(resultBundle.getEntryFirstRep().getResource());
255274
else
256275
{
257-
logger.warn("Error while search for Resource with url {}, bundle has no {} resource", url,
276+
logger.warn("Error while searching for Resource with url {}, bundle has no {} resource", url,
258277
resourceType.getAnnotation(ResourceDef.class).name());
259278

260279
if (resultBundle.getEntryFirstRep().getResponse().hasOutcome()
@@ -273,7 +292,7 @@ private Optional<Resource> findResourceInLocalFhirStore(String url, Class<? exte
273292
}
274293
catch (UnprocessableEntityException e)
275294
{
276-
logger.warn("Error while search for Resource with url {}, message: {}, status: {}", url, e.getMessage(),
295+
logger.warn("Error while searching for Resource with url {}, message: {}, status: {}", url, e.getMessage(),
277296
e.getStatusCode());
278297

279298
IBaseOperationOutcome outcome = e.getOperationOutcome();
@@ -285,18 +304,60 @@ private Optional<Resource> findResourceInLocalFhirStore(String url, Class<? exte
285304
}
286305
catch (BaseServerResponseException e)
287306
{
288-
logger.warn("Error while search for Resource with url {}, message: {}, status: {}", url, e.getMessage(),
307+
logger.warn("Error while searching for Resource with url {}, message: {}, status: {}", url, e.getMessage(),
289308
e.getStatusCode());
309+
310+
IBaseOperationOutcome outcome = e.getOperationOutcome();
311+
312+
if (outcome != null && outcome instanceof OperationOutcome)
313+
outcomeLogger.logOutcome((OperationOutcome) outcome);
314+
290315
throw e;
291316
}
292317
catch (Exception e)
293318
{
294-
logger.warn("Error while search for Resource with url " + url, e);
319+
logger.warn("Error while searching for Resource with url " + url, e);
295320
throw e;
296321
}
297322
}
298323

299-
private void update(Resource existingResource, Resource newResource, String bundleFullUrl)
324+
private Resource fixTemporaryReferences(Resource resource, Map<String, IdType> resourceIdsByUuid)
325+
{
326+
if (resource == null)
327+
return null;
328+
329+
else if (resource instanceof Observation)
330+
{
331+
if (resource.getMeta().getProfile().stream().map(CanonicalType::getValue)
332+
.anyMatch(url -> NUM_CODEX_BLOOD_GAS_PANEL.equals(url)
333+
|| (url != null && url.startsWith(NUM_CODEX_BLOOD_GAS_PANEL + "|"))))
334+
{
335+
Observation observation = (Observation) resource;
336+
List<Reference> members = observation.getHasMember();
337+
for (int i = 0; i < members.size(); i++)
338+
{
339+
Reference member = members.get(i);
340+
if (member.hasReference())
341+
{
342+
String uuid = member.getReference();
343+
IdType resourceId = resourceIdsByUuid.get(uuid);
344+
345+
if (resourceId != null)
346+
{
347+
logger.debug(
348+
"Replacing reference at Observation.hasMember[{}] from bundle resource {} with existing resource id",
349+
i, resource.getIdElement().getValue());
350+
member.setReferenceElement(resourceId.toUnqualifiedVersionless());
351+
}
352+
}
353+
}
354+
}
355+
}
356+
357+
return resource;
358+
}
359+
360+
private IdType update(Resource existingResource, Resource newResource, String bundleFullUrl)
300361
{
301362
logger.debug("Updating {}", newResource.getResourceType().name());
302363

@@ -309,7 +370,7 @@ private void update(Resource existingResource, Resource newResource, String bund
309370

310371
if (outcome.getId() == null)
311372
{
312-
logger.warn("Could not update {} {}", newResource.getResourceType().name(),
373+
logger.warn("Could not update {} {}: unknown reason", newResource.getResourceType().name(),
313374
newResource.getIdElement().toString());
314375
if (outcome.getOperationOutcome() != null && outcome.getOperationOutcome() instanceof OperationOutcome)
315376
outcomeLogger.logOutcome((OperationOutcome) outcome.getOperationOutcome());
@@ -318,7 +379,15 @@ private void update(Resource existingResource, Resource newResource, String bund
318379
+ newResource.getIdElement().toString());
319380
}
320381
else if (outcome.getOperationOutcome() != null && outcome.getOperationOutcome() instanceof OperationOutcome)
382+
{
321383
outcomeLogger.logOutcome((OperationOutcome) outcome.getOperationOutcome());
384+
logger.warn("Could not update {} {}: unknown reason", newResource.getResourceType().name(),
385+
newResource.getIdElement().toString());
386+
throw new RuntimeException("Could not create " + newResource.getResourceType().name() + " "
387+
+ newResource.getIdElement().toString() + ": unknown reason");
388+
}
389+
else
390+
return (IdType) outcome.getId();
322391
}
323392
catch (UnprocessableEntityException e)
324393
{
@@ -361,7 +430,7 @@ else if (outcome.getOperationOutcome() != null && outcome.getOperationOutcome()
361430
}
362431
}
363432

364-
private void create(Resource newResource, String bundleFullUrl)
433+
private IdType create(Resource newResource, String bundleFullUrl)
365434
{
366435
logger.debug("Creating {}", newResource.getResourceType().name());
367436

@@ -381,7 +450,15 @@ private void create(Resource newResource, String bundleFullUrl)
381450
+ newResource.getIdElement().toString());
382451
}
383452
else if (outcome.getOperationOutcome() != null && outcome.getOperationOutcome() instanceof OperationOutcome)
453+
{
384454
outcomeLogger.logOutcome((OperationOutcome) outcome.getOperationOutcome());
455+
logger.warn("Could not create {} {}: unknown reason", newResource.getResourceType().name(),
456+
newResource.getIdElement().toString());
457+
throw new RuntimeException("Could not create " + newResource.getResourceType().name() + " "
458+
+ newResource.getIdElement().toString() + ": unknown reason");
459+
}
460+
else
461+
return (IdType) outcome.getId();
385462
}
386463
catch (UnprocessableEntityException e)
387464
{

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ public Stream<DomainResource> getNewData(String pseudonym, DateWithPrecision exp
4949
logger.debug("Search-Bundle result: {}",
5050
geccoClient.getFhirContext().newJsonParser().encodeResourceToString(resultBundle));
5151

52-
return resultBundle.getEntry().stream().filter(BundleEntryComponent::hasResource)
52+
return distinctById(resultBundle.getEntry().stream().filter(BundleEntryComponent::hasResource)
5353
.map(BundleEntryComponent::getResource).filter(r -> r instanceof Bundle).map(r -> (Bundle) r)
54-
.flatMap(this::getDomainResources);
54+
.flatMap(this::getDomainResources));
5555
}
5656
}

0 commit comments

Comments
 (0)