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

Commit 452987b

Browse files
committed
Merge remote-tracking branch 'origin/develop' into
issues/100_upgrade_to_dsf_0.9.0
2 parents e3b9c9b + 5d2c196 commit 452987b

32 files changed

Lines changed: 680 additions & 402 deletions

codex-process-data-transfer/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<dependency>
2424
<groupId>org.highmed.dsf</groupId>
2525
<artifactId>dsf-tools-documentation-generator</artifactId>
26+
<scope>provided</scope>
2627
</dependency>
2728

2829
<!-- must be added as regular DSF plugin -->

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ public interface ConstantsDataTransfer
4444
String CODESYSTEM_NUM_CODEX_DATA_TRANSFER_VALUE_DATA_REFERENCE = "data-reference";
4545
String CODESYSTEM_NUM_CODEX_DATA_TRANSFER_VALUE_DRY_RUN = "dry-run";
4646
String CODESYSTEM_NUM_CODEX_DATA_TRANSFER_VALUE_ENCRYPTED_BUNDLE_SIZE = "encrypted-bundle-size";
47+
String CODESYSTEM_NUM_CODEX_DATA_TRANSFER_VALUE_LOCAL_VALIDATION_SUCCESSFUL = "local-validation-successful";
48+
String CODESYSTEM_NUM_CODEX_DATA_TRANSFER_VALUE_ENCRYPTED_BUNDLE_RESOURCES_COUNT = "encrypted-bundle-resources-count";
4749

4850
String PROFILE_NUM_CODEX_TASK_DATA_TRIGGER_PROCESS_URI = "http://www.netzwerk-universitaetsmedizin.de/bpe/Process/dataTrigger/";
4951
String PROFILE_NUM_CODEX_TASK_DATA_TRIGGER_PROCESS_URI_AND_LATEST_VERSION = PROFILE_NUM_CODEX_TASK_DATA_TRIGGER_PROCESS_URI

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ private static final class FttpClientStub implements FttpClient
3535
{
3636
private static final Logger logger = LoggerFactory.getLogger(FttpClientStub.class);
3737

38+
private static final String DIC_PSEUDONYM = "source2/original2";
3839
private static final Pattern DIC_PSEUDONYM_PATTERN = Pattern.compile(PSEUDONYM_PATTERN_STRING);
3940

4041
@Override
@@ -64,8 +65,9 @@ public Optional<String> getCrrPseudonym(String dicSourceAndPseudonym)
6465
@Override
6566
public Optional<String> getDicPseudonym(String bloomFilter)
6667
{
67-
logger.info("Requesting DIC pseudonym for bloom filter {} ", bloomFilter);
68-
return Optional.of("source2/original2");
68+
logger.warn("Returning simulated DIC pseudonym '{}' for bloom filter '{}', fTTP connection not configured.",
69+
DIC_PSEUDONYM, bloomFilter);
70+
return Optional.of(DIC_PSEUDONYM);
6971
}
7072

7173
@Override

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@ public void storeBundle(Bundle bundle)
5757
@Override
5858
public PatientReferenceList getPatientReferencesWithNewData(DateWithPrecision exportFrom, Date exportTo)
5959
{
60-
logger.warn("Returning demo pseudonyms for {}", geccoClient.getLocalIdentifierValue());
60+
logger.warn(
61+
"Returning four simulated patient references for {}, connection to GECCO FHIR server not configured",
62+
geccoClient.getLocalIdentifierValue());
6163

6264
PatientReference reference1 = PatientReference
6365
.from(new Identifier().setSystem(NAMING_SYSTEM_NUM_CODEX_DIC_PSEUDONYM).setValue("dic_foo/bar"));
@@ -79,7 +81,9 @@ public PatientReferenceList getPatientReferencesWithNewData(DateWithPrecision ex
7981
@Override
8082
public Stream<DomainResource> getNewData(String pseudonym, DateWithPrecision exportFrom, Date exportTo)
8183
{
82-
logger.warn("Returning demo resources for {}", pseudonym);
84+
logger.warn(
85+
"Returning simulated GECCO FHIR resources (Patient, Condition, Observation) for {}, connection to GECCO FHIR server not configured",
86+
pseudonym);
8387

8488
Patient p = geccoClient.getFhirContext().newJsonParser().parseResource(Patient.class, patient);
8589
p.setIdElement(new IdType(UUID.randomUUID().toString()));
@@ -102,6 +106,9 @@ public Stream<DomainResource> getNewData(String pseudonym, DateWithPrecision exp
102106
@Override
103107
public Optional<Patient> getPatient(String reference)
104108
{
109+
logger.warn("Returning simulated patient resource for {}, connection to GECCO FHIR server not configured",
110+
reference);
111+
105112
Patient p = geccoClient.getFhirContext().newJsonParser().parseResource(Patient.class, patient);
106113
p.addIdentifier().setSystem(ConstantsDataTransfer.NAMING_SYSTEM_NUM_CODEX_BLOOM_FILTER).setValue(
107114
"J75gYl+RiKSsxeu33tixBEEtFGCZwIEsWIKgvESaluvpSGBte/SBNZilz+sLSZdHSDKTL2J2d1yZsakqjtV5U2SMMJZ5IF3gEk1MT3sCRkxXEo1aJWKpnqndUTR+fvtSeMFj0y/O5yqrLV9zU79CNiTfZN5t1/6XGxZUXq2DovfCRrrpRxWjFwjKIDo0OkRANf7Mqp+Fsu0Un53JF57p/p1RLpWcJkC3xO+UslGbDo3mjgczdvxz0aLmWNA7/NIhk+Q50gxCX3B4QrntPfLLlBkrmIpsKRcLFVuYZik7pYZ9prd0qCLQ9tc8qiw1ry5kMfIvLnIS/FV36w==")
@@ -116,5 +123,7 @@ public Optional<Patient> getPatient(String reference)
116123
public void updatePatient(Patient patient)
117124
{
118125
// Nothing to do in stub client
126+
logger.info(
127+
"Not updating patient resource in GECCO FHIR server, connection to GECCO FHIR server not configured");
119128
}
120129
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.listener;
2+
3+
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.CODESYSTEM_NUM_CODEX_DATA_TRANSFER;
4+
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.CODESYSTEM_NUM_CODEX_DATA_TRANSFER_VALUE_LOCAL_VALIDATION_SUCCESSFUL;
5+
6+
import java.nio.charset.StandardCharsets;
7+
import java.text.SimpleDateFormat;
8+
import java.util.Objects;
9+
10+
import javax.activation.DataHandler;
11+
import javax.mail.internet.MimeBodyPart;
12+
import javax.mail.internet.MimeMultipart;
13+
import javax.mail.util.ByteArrayDataSource;
14+
15+
import org.camunda.bpm.engine.delegate.DelegateExecution;
16+
import org.camunda.bpm.engine.delegate.ExecutionListener;
17+
import org.highmed.dsf.bpe.service.MailService;
18+
import org.highmed.dsf.fhir.client.FhirWebserviceClientProvider;
19+
import org.highmed.dsf.fhir.task.TaskHelper;
20+
import org.hl7.fhir.r4.model.BooleanType;
21+
import org.hl7.fhir.r4.model.Task;
22+
import org.hl7.fhir.r4.model.Task.TaskOutputComponent;
23+
import org.hl7.fhir.r4.model.Task.TaskStatus;
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
26+
import org.springframework.beans.factory.InitializingBean;
27+
28+
import ca.uhn.fhir.context.FhirContext;
29+
import de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.DataTransferProcessPluginDefinition;
30+
31+
public class AfterDryRunEndListener implements ExecutionListener, InitializingBean
32+
{
33+
private static final Logger logger = LoggerFactory.getLogger(AfterDryRunEndListener.class);
34+
35+
private final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
36+
37+
private final TaskHelper taskHelper;
38+
private final FhirWebserviceClientProvider clientProvider;
39+
private final FhirContext fhirContext;
40+
private final MailService mailService;
41+
42+
private final String localOrganizationIdentifierValue;
43+
private final boolean sendDryRunSuccessMail;
44+
45+
public AfterDryRunEndListener(TaskHelper taskHelper, FhirWebserviceClientProvider clientProvider,
46+
FhirContext fhirContext, MailService mailService, String localOrganizationIdentifierValue,
47+
boolean sendDryRunSuccessMail)
48+
{
49+
this.taskHelper = taskHelper;
50+
this.clientProvider = clientProvider;
51+
this.fhirContext = fhirContext;
52+
this.mailService = mailService;
53+
54+
this.localOrganizationIdentifierValue = localOrganizationIdentifierValue;
55+
this.sendDryRunSuccessMail = sendDryRunSuccessMail;
56+
}
57+
58+
@Override
59+
public void afterPropertiesSet() throws Exception
60+
{
61+
Objects.requireNonNull(taskHelper, "taskHelper");
62+
Objects.requireNonNull(clientProvider, "clientProvider");
63+
Objects.requireNonNull(fhirContext, "fhirContext");
64+
Objects.requireNonNull(mailService, "mailService");
65+
66+
Objects.requireNonNull(localOrganizationIdentifierValue, "localOrganizationIdentifierValue");
67+
}
68+
69+
@Override
70+
public void notify(DelegateExecution execution) throws Exception
71+
{
72+
if (!sendDryRunSuccessMail)
73+
return;
74+
75+
Task task = taskHelper.getLeadingTaskFromExecutionVariables(execution);
76+
Task finalTask = clientProvider.getLocalWebserviceClient().read(Task.class, task.getIdElement().getIdPart());
77+
78+
if (!TaskStatus.COMPLETED.equals(finalTask.getStatus()))
79+
{
80+
logger.warn("Final Task from DSF FHIR server not in status {} but {}, not sending dry-run success mail",
81+
TaskStatus.COMPLETED, finalTask.getStatus());
82+
return;
83+
}
84+
85+
if (!isLocalValidationSuccessful(finalTask))
86+
{
87+
logger.warn(
88+
"Final Task from DSF FHIR server missing '{}' output parameter with value 'true', not sending dry-run success mail",
89+
CODESYSTEM_NUM_CODEX_DATA_TRANSFER_VALUE_LOCAL_VALIDATION_SUCCESSFUL);
90+
return;
91+
}
92+
93+
String finalTaskAsXml = fhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(finalTask);
94+
String attachmentFilename = finalTask.getIdElement().getIdPart() + ".xml";
95+
96+
MimeBodyPart text = new MimeBodyPart();
97+
text.setText(createMesssage(finalTask, attachmentFilename));
98+
99+
MimeBodyPart attachment = new MimeBodyPart();
100+
attachment.setFileName(attachmentFilename);
101+
attachment.setDataHandler(new DataHandler(
102+
new ByteArrayDataSource(finalTaskAsXml.getBytes(StandardCharsets.UTF_8), "application/xml")));
103+
104+
MimeMultipart body = new MimeMultipart();
105+
body.addBodyPart(text);
106+
body.addBodyPart(attachment);
107+
108+
MimeBodyPart message = new MimeBodyPart();
109+
message.setContent(body);
110+
111+
mailService.send("Dry-Run Success: " + localOrganizationIdentifierValue, message);
112+
}
113+
114+
private boolean isLocalValidationSuccessful(Task task)
115+
{
116+
return task.getOutput().stream().filter(TaskOutputComponent::hasType).filter(o -> o.getType().hasCoding())
117+
.filter(o -> o.getType().getCoding().stream()
118+
.anyMatch(c -> CODESYSTEM_NUM_CODEX_DATA_TRANSFER.equals(c.getSystem())
119+
&& CODESYSTEM_NUM_CODEX_DATA_TRANSFER_VALUE_LOCAL_VALIDATION_SUCCESSFUL
120+
.equals(c.getCode())))
121+
.filter(TaskOutputComponent::hasValue).filter(o -> o.getValue() instanceof BooleanType)
122+
.map(o -> (BooleanType) o.getValue()).anyMatch(b -> Boolean.TRUE.equals(b.getValue()));
123+
}
124+
125+
private String createMesssage(Task finalTask, String attachmentFileName)
126+
{
127+
StringBuilder b = new StringBuilder();
128+
129+
b.append("Send process version ");
130+
b.append(DataTransferProcessPluginDefinition.VERSION);
131+
b.append(" dry-run at organization with identifier '");
132+
b.append(localOrganizationIdentifierValue);
133+
b.append("' successfully completed on ");
134+
b.append(DATE_FORMAT.format(finalTask.getMeta().getLastUpdated()));
135+
b.append(".\n\nTask resource is attached as '");
136+
b.append(attachmentFileName);
137+
b.append("'");
138+
139+
return b.toString();
140+
}
141+
}

codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/service/send/EncryptData.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.BPMN_EXECUTION_VARIABLE_AES_RETURN_KEY;
44
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.BPMN_EXECUTION_VARIABLE_BUNDLE;
55
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.BPMN_EXECUTION_VARIABLE_PATIENT_REFERENCE;
6+
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.CODESYSTEM_NUM_CODEX_DATA_TRANSFER;
67
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.CODESYSTEM_NUM_CODEX_DATA_TRANSFER_ERROR_VALUE_ECRYPTION_OF_GECCO_DATA_FOR_CRR_FAILED;
8+
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.CODESYSTEM_NUM_CODEX_DATA_TRANSFER_VALUE_ENCRYPTED_BUNDLE_SIZE;
79
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.NAMING_SYSTEM_NUM_CODEX_CRR_PSEUDONYM;
810
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.NAMING_SYSTEM_NUM_CODEX_DIC_PSEUDONYM;
911
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.PSEUDONYM_PLACEHOLDER;
@@ -29,6 +31,8 @@
2931
import org.highmed.dsf.fhir.task.TaskHelper;
3032
import org.highmed.pseudonymization.crypto.AesGcmUtil;
3133
import org.hl7.fhir.r4.model.Bundle;
34+
import org.hl7.fhir.r4.model.Task;
35+
import org.hl7.fhir.r4.model.UnsignedIntType;
3236
import org.slf4j.Logger;
3337
import org.slf4j.LoggerFactory;
3438

@@ -82,6 +86,8 @@ protected void doExecute(DelegateExecution execution) throws BpmnError, Exceptio
8286

8387
execution.setVariable(BPMN_EXECUTION_VARIABLE_AES_RETURN_KEY, Variables.byteArrayValue(returnKey));
8488
execution.setVariable(BPMN_EXECUTION_VARIABLE_BUNDLE, Variables.byteArrayValue(encrypted));
89+
90+
addEncryptedBundleSizeToTask(execution, encrypted.length);
8591
}
8692
catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
8793
| InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException
@@ -93,6 +99,15 @@ protected void doExecute(DelegateExecution execution) throws BpmnError, Exceptio
9399
}
94100
}
95101

102+
private void addEncryptedBundleSizeToTask(DelegateExecution execution, int encryptedSize)
103+
{
104+
Task task = getLeadingTaskFromExecutionVariables(execution);
105+
task.addOutput().setValue(new UnsignedIntType(encryptedSize)).getType().getCodingFirstRep()
106+
.setSystem(CODESYSTEM_NUM_CODEX_DATA_TRANSFER)
107+
.setCode(CODESYSTEM_NUM_CODEX_DATA_TRANSFER_VALUE_ENCRYPTED_BUNDLE_SIZE);
108+
updateLeadingTaskInExecutionVariables(execution, task);
109+
}
110+
96111
private byte[] toByteArray(String pseudonym, Bundle bundle) throws IOException
97112
{
98113
String bundleString = fhirContext.newJsonParser().encodeResourceToString(bundle);

codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/service/send/LogDryRunSuccess.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
package de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.service.send;
22

3+
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.CODESYSTEM_NUM_CODEX_DATA_TRANSFER;
4+
import static de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.ConstantsDataTransfer.CODESYSTEM_NUM_CODEX_DATA_TRANSFER_VALUE_LOCAL_VALIDATION_SUCCESSFUL;
5+
36
import org.camunda.bpm.engine.delegate.BpmnError;
47
import org.camunda.bpm.engine.delegate.DelegateExecution;
58
import org.highmed.dsf.bpe.delegate.AbstractServiceDelegate;
69
import org.highmed.dsf.fhir.authorization.read.ReadAccessHelper;
710
import org.highmed.dsf.fhir.client.FhirWebserviceClientProvider;
811
import org.highmed.dsf.fhir.task.TaskHelper;
12+
import org.hl7.fhir.r4.model.BooleanType;
13+
import org.hl7.fhir.r4.model.Task;
14+
import org.hl7.fhir.r4.model.Task.TaskOutputComponent;
15+
import org.hl7.fhir.r4.model.Task.TaskStatus;
916
import org.slf4j.Logger;
1017
import org.slf4j.LoggerFactory;
1118

@@ -22,6 +29,28 @@ public LogDryRunSuccess(FhirWebserviceClientProvider clientProvider, TaskHelper
2229
@Override
2330
protected void doExecute(DelegateExecution execution) throws BpmnError, Exception
2431
{
25-
logger.info("Dry run successfully completed");
32+
Task task = getLeadingTaskFromExecutionVariables(execution);
33+
if (isLocalValidationSuccessful(task))
34+
{
35+
logger.info("Send process dry-run successfully completed");
36+
}
37+
else
38+
{
39+
logger.warn("Send process dry-run unsuccessful");
40+
41+
task.setStatus(TaskStatus.FAILED);
42+
updateLeadingTaskInExecutionVariables(execution, task);
43+
}
44+
}
45+
46+
private boolean isLocalValidationSuccessful(Task task)
47+
{
48+
return task.getOutput().stream().filter(TaskOutputComponent::hasType).filter(o -> o.getType().hasCoding())
49+
.filter(o -> o.getType().getCoding().stream()
50+
.anyMatch(c -> CODESYSTEM_NUM_CODEX_DATA_TRANSFER.equals(c.getSystem())
51+
&& CODESYSTEM_NUM_CODEX_DATA_TRANSFER_VALUE_LOCAL_VALIDATION_SUCCESSFUL
52+
.equals(c.getCode())))
53+
.filter(TaskOutputComponent::hasValue).filter(o -> o.getValue() instanceof BooleanType)
54+
.map(o -> (BooleanType) o.getValue()).anyMatch(b -> Boolean.TRUE.equals(b.getValue()));
2655
}
2756
}

0 commit comments

Comments
 (0)