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

Commit 5d2c196

Browse files
committed
Merge remote-tracking branch 'origin/issues/107_dry_run_success_mail'
into develop
2 parents f97fbbd + 056f8a8 commit 5d2c196

7 files changed

Lines changed: 223 additions & 1 deletion

File tree

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 -->
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/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
}

codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/spring/config/SendConfig.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import org.springframework.context.annotation.Bean;
55
import org.springframework.context.annotation.Configuration;
66

7+
import de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.listener.AfterDryRunEndListener;
78
import de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.message.StartTranslateProcess;
89
import de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.service.send.CheckConsent;
910
import de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.service.send.CheckDryRun;
@@ -106,6 +107,15 @@ public LogDryRunSuccess logDryRunSuccess()
106107
transferDataConfig.readAccessHelper());
107108
}
108109

110+
@Bean
111+
public AfterDryRunEndListener afterDryRunEndListener()
112+
{
113+
return new AfterDryRunEndListener(transferDataConfig.taskHelper(), transferDataConfig.fhirClientProvider(),
114+
transferDataConfig.fhirContext(), transferDataConfig.mailService(),
115+
transferDataConfig.organizationProvider().getLocalIdentifierValue(),
116+
transferDataConfig.getSendDryRunSuccessMail());
117+
}
118+
109119
@Bean
110120
public StoreDataForGth storeDataForGth()
111121
{

codex-process-data-transfer/src/main/java/de/netzwerk_universitaetsmedizin/codex/processes/data_transfer/spring/config/TransferDataConfig.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,10 @@ public class TransferDataConfig
294294
@Value("${de.netzwerk.universitaetsmedizin.codex.mail.sendProcessFailedMails:false}")
295295
private boolean sendProcessFailedMail;
296296

297+
@ProcessDocumentation(description = "To enable a mail being send if a 'send' process dry-run was successful, the success mail will include the 'completed' task resource as an attachment, set to 'true'. This requires the SMPT mail service client to be configured in the DSF", processNames = "wwwnetzwerk-universitaetsmedizinde_dataSend")
298+
@Value("${de.netzwerk.universitaetsmedizin.codex.mail.sendDryRunSuccessMail:false}")
299+
private boolean sendDryRunSuccessMail;
300+
297301
@Value("${org.highmed.dsf.bpe.fhir.server.organization.identifier.value}")
298302
private String localIdentifierValue;
299303

@@ -352,6 +356,16 @@ public OrganizationProvider organizationProvider()
352356
return organizationProvider;
353357
}
354358

359+
public MailService mailService()
360+
{
361+
return mailService;
362+
}
363+
364+
public boolean getSendDryRunSuccessMail()
365+
{
366+
return sendDryRunSuccessMail;
367+
}
368+
355369
@Bean
356370
public CrrKeyProvider crrKeyProvider()
357371
{

codex-process-data-transfer/src/main/resources/bpe/send.bpmn

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,9 @@
285285
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">${dryRun}</bpmn:conditionExpression>
286286
</bpmn:sequenceFlow>
287287
<bpmn:endEvent id="Event_0ww5gaz">
288+
<bpmn:extensionElements>
289+
<camunda:executionListener class="de.netzwerk_universitaetsmedizin.codex.processes.data_transfer.listener.AfterDryRunEndListener" event="end" />
290+
</bpmn:extensionElements>
288291
<bpmn:incoming>Flow_13ulpon</bpmn:incoming>
289292
</bpmn:endEvent>
290293
<bpmn:sequenceFlow id="Flow_13ulpon" sourceRef="LogDryRunSuccess" targetRef="Event_0ww5gaz" />

codex-processes-ap1-docker-test-setup/docker-compose.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ services:
6464
target: /docker-entrypoint-initdb.d/init-db.sh
6565
read_only: true
6666

67+
mailhog:
68+
image: mailhog/mailhog
69+
restart: "no"
70+
ports:
71+
- 127.0.0.1:8025:8025 # web ui
72+
networks:
73+
internet:
74+
6775

6876
dic-fhir:
6977
image: ghcr.io/highmed/fhir:0.9.0-RC1
@@ -158,6 +166,10 @@ services:
158166
ORG_HIGHMED_DSF_BPE_DB_USER_CAMUNDA_USERNAME: dic_camunda_server_user
159167
ORG_HIGHMED_DSF_BPE_FHIR_SERVER_ORGANIZATION_IDENTIFIER_VALUE: Test_DIC
160168
ORG_HIGHMED_DSF_BPE_FHIR_SERVER_BASE_URL: https://dic/fhir
169+
ORG_HIGHMED_DSF_BPE_MAIL_HOST: mailhog
170+
ORG_HIGHMED_DSF_BPE_MAIL_PORT: 1025
171+
ORG_HIGHMED_DSF_BPE_MAIL_FROMADDRESS: bpe@dic
172+
ORG_HIGHMED_DSF_BPE_MAIL_TOADDRESSES: bpe@dic
161173
ORG_HIGHMED_DSF_BPE_PROCESS_EXCLUDED: |
162174
wwwnetzwerk-universitaetsmedizinde_dataTranslate/${PROCESS_VERSION}
163175
wwwnetzwerk-universitaetsmedizinde_dataReceive/${PROCESS_VERSION}
@@ -168,13 +180,15 @@ services:
168180
DE_NETZWERK_UNIVERSITAETSMEDIZIN_CODEX_GECCO_VALIDATION_STRUCTUREDEFINITION_CACHEFOLDER: /opt/bpe/cache/StructureDefinition/
169181
DE_NETZWERK_UNIVERSITAETSMEDIZIN_CODEX_GECCO_VALIDATION_VALUESET_CACHEFOLDER: /opt/bpe/cache/ValueSet/
170182
DE_NETZWERK_UNIVERSITAETSMEDIZIN_CODEX_GECCO_VALIDATION_VALUESET_EXPANSION_SERVER_BASEURL: https://r4.ontoserver.csiro.au/fhir
183+
DE_NETZWERK_UNIVERSITAETSMEDIZIN_CODEX_MAIL_SENDDRYRUNSUCCESSMAIL: 'true'
171184
networks:
172185
dic-bpe-frontend:
173186
dic-bpe-backend:
174187
internet:
175188
depends_on:
176189
- db
177190
- dic-fhir
191+
- mailhog
178192
# - dic-fhir-store not defining a dependency here, dic-fhir-store* needs to be started manually
179193
dic-fhir-store-hapi:
180194
build: ./dic/hapi
@@ -288,6 +302,10 @@ services:
288302
ORG_HIGHMED_DSF_BPE_DB_USER_CAMUNDA_USERNAME: gth_camunda_server_user
289303
ORG_HIGHMED_DSF_BPE_FHIR_SERVER_ORGANIZATION_IDENTIFIER_VALUE: Test_GTH
290304
ORG_HIGHMED_DSF_BPE_FHIR_SERVER_BASE_URL: https://gth/fhir
305+
ORG_HIGHMED_DSF_BPE_MAIL_HOST: mailhog
306+
ORG_HIGHMED_DSF_BPE_MAIL_PORT: 1025
307+
ORG_HIGHMED_DSF_BPE_MAIL_FROMADDRESS: bpe@gth
308+
ORG_HIGHMED_DSF_BPE_MAIL_TOADDRESSES: bpe@gth
291309
ORG_HIGHMED_DSF_BPE_PROCESS_EXCLUDED: |
292310
wwwnetzwerk-universitaetsmedizinde_dataTrigger/${PROCESS_VERSION}
293311
wwwnetzwerk-universitaetsmedizinde_dataSend/${PROCESS_VERSION}
@@ -301,6 +319,7 @@ services:
301319
depends_on:
302320
- db
303321
- gth-fhir
322+
- mailhog
304323

305324

306325
crr-fhir:
@@ -393,6 +412,10 @@ services:
393412
ORG_HIGHMED_DSF_BPE_DB_USER_CAMUNDA_USERNAME: crr_camunda_server_user
394413
ORG_HIGHMED_DSF_BPE_FHIR_SERVER_ORGANIZATION_IDENTIFIER_VALUE: Test_CRR
395414
ORG_HIGHMED_DSF_BPE_FHIR_SERVER_BASE_URL: https://crr/fhir
415+
ORG_HIGHMED_DSF_BPE_MAIL_HOST: mailhog
416+
ORG_HIGHMED_DSF_BPE_MAIL_PORT: 1025
417+
ORG_HIGHMED_DSF_BPE_MAIL_FROMADDRESS: bpe@crr
418+
ORG_HIGHMED_DSF_BPE_MAIL_TOADDRESSES: bpe@crr
396419
ORG_HIGHMED_DSF_BPE_PROCESS_EXCLUDED: |
397420
wwwnetzwerk-universitaetsmedizinde_dataTrigger/${PROCESS_VERSION}
398421
wwwnetzwerk-universitaetsmedizinde_dataSend/${PROCESS_VERSION}
@@ -408,6 +431,7 @@ services:
408431
depends_on:
409432
- db
410433
- crr-fhir
434+
- mailhog
411435
# - crr-fhir-bridge not defining a dependency here, crr-fhir-bridge* needs to be started manually
412436
crr-ehrbase-db:
413437
image: ehrbase/ehrbase-postgres

0 commit comments

Comments
 (0)