diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/MailActivityBehavior.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/MailActivityBehavior.java index 397f0a6dbab..715c3aea183 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/MailActivityBehavior.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/MailActivityBehavior.java @@ -18,11 +18,14 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import javax.activation.DataSource; import javax.naming.NamingException; @@ -46,6 +49,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; + /** * Based on the MailActivityBehavior found in the bpmn engine, adapted for use in cmmn. * @@ -83,10 +89,10 @@ public void execute(CommandContext commandContext, PlanItemInstanceEntity planIt Email email = null; try { String headersStr = getStringFromField(headers, planItemInstanceEntity); - String toStr = getStringFromField(to, planItemInstanceEntity); + Collection toList = parseRecipients(to, planItemInstanceEntity); String fromStr = getStringFromField(from, planItemInstanceEntity); - String ccStr = getStringFromField(cc, planItemInstanceEntity); - String bccStr = getStringFromField(bcc, planItemInstanceEntity); + Collection ccList = parseRecipients(cc, planItemInstanceEntity); + Collection bccList = parseRecipients(bcc, planItemInstanceEntity); String subjectStr = getStringFromField(subject, planItemInstanceEntity); String textStr = textVar == null ? getStringFromField(text, planItemInstanceEntity) : getStringFromField(getExpression(commandContext, planItemInstanceEntity, textVar), planItemInstanceEntity); @@ -97,16 +103,16 @@ public void execute(CommandContext commandContext, PlanItemInstanceEntity planIt List dataSources = new LinkedList<>(); getFilesFromFields(attachments, planItemInstanceEntity, files, dataSources); - if (StringUtils.isAllEmpty(toStr, ccStr, bccStr)) { + if (toList.isEmpty() && ccList.isEmpty() && bccList.isEmpty()) { throw new FlowableException("No recipient could be found for sending email"); } email = createEmail(textStr, htmlStr, attachmentsExist(files, dataSources)); addHeader(email, headersStr); - addTo(commandContext, email, toStr, planItemInstanceEntity.getTenantId()); + addTo(email, toList, planItemInstanceEntity.getTenantId(), commandContext); setFrom(commandContext, email, fromStr, planItemInstanceEntity.getTenantId()); - addCc(commandContext, email, ccStr, planItemInstanceEntity.getTenantId()); - addBcc(commandContext, email, bccStr, planItemInstanceEntity.getTenantId()); + addCc(email, ccList, planItemInstanceEntity.getTenantId(), commandContext); + addBcc(email, bccList, planItemInstanceEntity.getTenantId(), commandContext); setSubject(email, subjectStr); setMailServerProperties(commandContext, email, planItemInstanceEntity.getTenantId()); setCharset(email, charSetStr, planItemInstanceEntity.getTenantId()); @@ -190,17 +196,17 @@ protected MultiPartEmail createMultiPartEmail(String text) { } } - protected void addTo(CommandContext commandContext, Email email, String to, String tenantId) { - if (to == null) { + protected void addTo(Email email, Collection to, String tenantId, CommandContext commandContext) { + if (to == null || to.isEmpty()) { return; } - String newTo = getForceTo(commandContext, tenantId); - if (newTo == null) { - newTo = to; + Collection newTo = to; + Collection forceTo = getForceTo(commandContext, tenantId); + if (forceTo != null && !forceTo.isEmpty()) { + newTo = forceTo; } - String[] tos = splitAndTrim(newTo); - if (tos != null) { - for (String t : tos) { + if (!newTo.isEmpty()) { + for (String t : newTo) { try { email.addTo(t); } catch (EmailException e) { @@ -238,18 +244,18 @@ protected void setFrom(CommandContext commandContext, Email email, String from, } } - protected void addCc(CommandContext commandContext, Email email, String cc, String tenantId) { - if (cc == null) { + protected void addCc(Email email, Collection cc, String tenantId, CommandContext commandContext) { + if (cc == null || cc.isEmpty()) { return; } + Collection newCc = cc; - String newCc = getForceTo(commandContext, tenantId); - if (newCc == null) { - newCc = cc; + Collection forceTo = getForceTo(commandContext, tenantId); + if (forceTo != null && !forceTo.isEmpty()) { + newCc = forceTo; } - String[] ccs = splitAndTrim(newCc); - if (ccs != null) { - for (String c : ccs) { + if (!newCc.isEmpty()) { + for (String c : newCc) { try { email.addCc(c); } catch (EmailException e) { @@ -259,17 +265,17 @@ protected void addCc(CommandContext commandContext, Email email, String cc, Stri } } - protected void addBcc(CommandContext commandContext, Email email, String bcc, String tenantId) { - if (bcc == null) { + protected void addBcc(Email email, Collection bcc, String tenantId, CommandContext commandContext) { + if (bcc == null || bcc.isEmpty()) { return; } - String newBcc = getForceTo(commandContext, tenantId); - if (newBcc == null) { - newBcc = bcc; + Collection newBcc = bcc; + Collection forceTo = getForceTo(commandContext, tenantId); + if (forceTo != null && !forceTo.isEmpty()) { + newBcc = forceTo; } - String[] bccs = splitAndTrim(newBcc); - if (bccs != null) { - for (String b : bccs) { + if (!newBcc.isEmpty()) { + for (String b : newBcc) { try { email.addBcc(b); } catch (EmailException e) { @@ -378,13 +384,9 @@ protected void setCharset(Email email, String charSetStr, String tenantId) { } } - protected String[] splitAndTrim(String str) { + protected Collection splitAndTrim(String str) { if (str != null) { - String[] splittedStrings = str.split(","); - for (int i = 0; i < splittedStrings.length; i++) { - splittedStrings[i] = splittedStrings[i].trim(); - } - return splittedStrings; + return Arrays.stream(str.split(",")).map(String::trim).collect(Collectors.toList()); } return null; } @@ -399,6 +401,32 @@ protected String getStringFromField(Expression expression, PlanItemInstanceEntit return null; } + protected Collection parseRecipients(Expression expression, PlanItemInstanceEntity planItemInstanceEntity) { + if (expression == null) { + return Collections.emptyList(); + } + Object value = expression.getValue(planItemInstanceEntity); + if (value == null) { + return Collections.emptyList(); + } + if (value instanceof Collection) { + return (Collection) value; + } else if (value instanceof ArrayNode) { + ArrayNode arrayNode = (ArrayNode) value; + Collection recipients = new ArrayList<>(arrayNode.size()); + for (JsonNode node : arrayNode) { + recipients.add(node.asText()); + } + return recipients; + } else { + String str = value.toString(); + if (StringUtils.isNotEmpty(str)) { + return Arrays.asList(value.toString().split("[\\s]*,[\\s]*")); + } + } + return Collections.emptyList(); + } + protected void getFilesFromFields(Expression expression, PlanItemInstanceEntity planItemInstanceEntity, List files, List dataSources) { if (expression == null) { @@ -489,7 +517,7 @@ protected void handleException(PlanItemInstanceEntity planItemInstanceEntity, St } } - protected String getForceTo(CommandContext commandContext, String tenantId) { + protected Collection getForceTo(CommandContext commandContext, String tenantId) { String forceTo = null; if (tenantId != null && tenantId.length() > 0) { Map mailServers = CommandContextUtil.getCmmnEngineConfiguration(commandContext).getMailServers(); @@ -503,7 +531,7 @@ protected String getForceTo(CommandContext commandContext, String tenantId) { forceTo = CommandContextUtil.getCmmnEngineConfiguration(commandContext).getMailServerForceTo(); } - return forceTo; + return splitAndTrim(forceTo); } protected Charset getDefaultCharset(String tenantId) { diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/task/CmmnMailTaskTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/task/CmmnMailTaskTest.java index 0ce8976b748..27bfb07514d 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/task/CmmnMailTaskTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/task/CmmnMailTaskTest.java @@ -22,6 +22,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -41,6 +42,9 @@ import org.subethamail.wiser.Wiser; import org.subethamail.wiser.WiserMessage; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; + /** * @author Joram Barrez */ @@ -162,6 +166,44 @@ public void testTextMailExpressions() { } + @Test + @CmmnDeployment(resources = "org/flowable/cmmn/test/task/CmmnMailTaskTest.testTextMailExpressions.cmmn") + public void testDynamicRecipientsStringList() throws MessagingException { + String recipients = "flowable@localhost, misspiggy@flowable.org"; + testDynamicRecipientsInternal(recipients); + } + + @Test + @CmmnDeployment(resources = "org/flowable/cmmn/test/task/CmmnMailTaskTest.testTextMailExpressions.cmmn") + public void testDynamicRecipientsArrayList() throws MessagingException { + List recipients = Arrays.asList("flowable@localhost", "misspiggy@flowable.org"); + testDynamicRecipientsInternal(recipients); + } + + @Test + @CmmnDeployment(resources = "org/flowable/cmmn/test/task/CmmnMailTaskTest.testTextMailExpressions.cmmn") + public void testDynamicRecipientsArrayNode() throws MessagingException { + ArrayNode recipients = new ObjectMapper().createArrayNode().add("flowable@localhost").add("misspiggy@flowable.org"); + testDynamicRecipientsInternal(recipients); + } + + private void testDynamicRecipientsInternal(Object recipients) throws MessagingException { + cmmnRuntimeService.createCaseInstanceBuilder() + .caseDefinitionKey("testMail") + .variable("toVar", recipients) + .variable("fromVar", "from@flowable.org") + .variable("ccVar", recipients) + .variable("bccVar", recipients) + .variable("subjectVar", "Testing") + .variable("bodyVar", "The test body") + .start(); + List messages = wiser.getMessages(); + MimeMessage mimeMessage = messages.get(0).getMimeMessage(); + assertThat(mimeMessage.getHeader("To", null)).isEqualTo("flowable@localhost, misspiggy@flowable.org"); + assertThat(mimeMessage.getHeader("Cc", null)).isEqualTo("flowable@localhost, misspiggy@flowable.org"); + + } + @Test @CmmnDeployment public void testCcBccWithoutTo() { diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/MailActivityBehavior.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/MailActivityBehavior.java index 37cf4f2b8ba..67633dee3e5 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/MailActivityBehavior.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/behavior/MailActivityBehavior.java @@ -18,11 +18,14 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import javax.activation.DataSource; import javax.naming.NamingException; @@ -48,6 +51,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; + /** * @author Joram Barrez * @author Frederik Heremans @@ -88,17 +94,17 @@ public void execute(DelegateExecution execution) { skipExpressionText = serviceTask.getSkipExpression(); isSkipExpressionEnabled = SkipExpressionUtil.isSkipExpressionEnabled(skipExpressionText, flowElement.getId(), execution, commandContext); } - + if (!isSkipExpressionEnabled || !SkipExpressionUtil.shouldSkipFlowElement(skipExpressionText, flowElement.getId(), execution, commandContext)) { boolean doIgnoreException = Boolean.parseBoolean(getStringFromField(ignoreException, execution)); String exceptionVariable = getStringFromField(exceptionVariableName, execution); Email email = null; try { String headersStr = getStringFromField(headers, execution); - String toStr = getStringFromField(to, execution); + Collection toList = parseRecipients(to, execution); String fromStr = getStringFromField(from, execution); - String ccStr = getStringFromField(cc, execution); - String bccStr = getStringFromField(bcc, execution); + Collection ccList = parseRecipients(cc, execution); + Collection bccList = parseRecipients(bcc, execution); String subjectStr = getStringFromField(subject, execution); String textStr = textVar == null ? getStringFromField(text, execution) : getStringFromField(getExpression(execution, textVar), execution); String htmlStr = htmlVar == null ? getStringFromField(html, execution) : getStringFromField(getExpression(execution, htmlVar), execution); @@ -107,23 +113,23 @@ public void execute(DelegateExecution execution) { List dataSources = new LinkedList<>(); getFilesFromFields(attachments, execution, files, dataSources); - if (StringUtils.isAllEmpty(toStr, ccStr, bccStr)) { + if (toList.isEmpty() && ccList.isEmpty() && bccList.isEmpty()) { throw new FlowableException("No recipient could be found for sending email"); } email = createEmail(textStr, htmlStr, attachmentsExist(files, dataSources)); addHeader(email, headersStr); - addTo(email, toStr, execution.getTenantId()); + addTo(email, toList, execution.getTenantId()); setFrom(email, fromStr, execution.getTenantId()); - addCc(email, ccStr, execution.getTenantId()); - addBcc(email, bccStr, execution.getTenantId()); + addCc(email, ccList, execution.getTenantId()); + addBcc(email, bccList, execution.getTenantId()); setSubject(email, subjectStr); setMailServerProperties(email, execution.getTenantId()); setCharset(email, charSetStr, execution.getTenantId()); attach(email, files, dataSources); - + email.send(); - + } catch (FlowableException e) { handleException(execution, e.getMessage(), e, doIgnoreException, exceptionVariable); } catch (EmailException e) { @@ -200,17 +206,17 @@ protected MultiPartEmail createMultiPartEmail(String text) { } } - protected void addTo(Email email, String to, String tenantId) { - if (to == null) { + protected void addTo(Email email, Collection to, String tenantId) { + if (to == null || to.isEmpty()) { return; } - String newTo = getForceTo(tenantId); - if (newTo == null) { - newTo = to; + Collection newTo = to; + Collection forceTo = getForceTo(tenantId); + if (forceTo != null && !forceTo.isEmpty()) { + newTo = forceTo; } - String[] tos = splitAndTrim(newTo); - if (tos != null) { - for (String t : tos) { + if (!newTo.isEmpty()) { + for (String t : newTo) { try { email.addTo(t); } catch (EmailException e) { @@ -248,18 +254,18 @@ protected void setFrom(Email email, String from, String tenantId) { } } - protected void addCc(Email email, String cc, String tenantId) { - if (cc == null) { + protected void addCc(Email email, Collection cc, String tenantId) { + if (cc == null || cc.isEmpty()) { return; } + Collection newCc = cc; - String newCc = getForceTo(tenantId); - if (newCc == null) { - newCc = cc; + Collection forceTo = getForceTo(tenantId); + if (forceTo != null && !forceTo.isEmpty()) { + newCc = forceTo; } - String[] ccs = splitAndTrim(newCc); - if (ccs != null) { - for (String c : ccs) { + if (!newCc.isEmpty()) { + for (String c : newCc) { try { email.addCc(c); } catch (EmailException e) { @@ -269,17 +275,17 @@ protected void addCc(Email email, String cc, String tenantId) { } } - protected void addBcc(Email email, String bcc, String tenantId) { - if (bcc == null) { + protected void addBcc(Email email, Collection bcc, String tenantId) { + if (bcc == null || bcc.isEmpty()) { return; } - String newBcc = getForceTo(tenantId); - if (newBcc == null) { - newBcc = bcc; + Collection newBcc = bcc; + Collection forceTo = getForceTo(tenantId); + if (forceTo != null && !forceTo.isEmpty()) { + newBcc = forceTo; } - String[] bccs = splitAndTrim(newBcc); - if (bccs != null) { - for (String b : bccs) { + if (!newBcc.isEmpty()) { + for (String b : newBcc) { try { email.addBcc(b); } catch (EmailException e) { @@ -388,13 +394,9 @@ protected void setCharset(Email email, String charSetStr, String tenantId) { } } - protected String[] splitAndTrim(String str) { + protected Collection splitAndTrim(String str) { if (str != null) { - String[] splittedStrings = str.split(","); - for (int i = 0; i < splittedStrings.length; i++) { - splittedStrings[i] = splittedStrings[i].trim(); - } - return splittedStrings; + return Arrays.stream(str.split(",")).map(String::trim).collect(Collectors.toList()); } return null; } @@ -409,6 +411,32 @@ protected String getStringFromField(Expression expression, DelegateExecution exe return null; } + protected Collection parseRecipients(Expression expression, DelegateExecution execution) { + if (expression == null) { + return Collections.emptyList(); + } + Object value = expression.getValue(execution); + if (value == null) { + return Collections.emptyList(); + } + if (value instanceof Collection) { + return (Collection) value; + } else if (value instanceof ArrayNode) { + ArrayNode arrayNode = (ArrayNode) value; + Collection recipients = new ArrayList<>(arrayNode.size()); + for (JsonNode node : arrayNode) { + recipients.add(node.asText()); + } + return recipients; + } else { + String str = value.toString(); + if (StringUtils.isNotEmpty(str)) { + return Arrays.asList(value.toString().split("[\\s]*,[\\s]*")); + } + } + return Collections.emptyList(); + } + protected void getFilesFromFields(Expression expression, DelegateExecution execution, List files, List dataSources) { if (expression == null) { @@ -499,7 +527,7 @@ protected void handleException(DelegateExecution execution, String msg, Exceptio } } - protected String getForceTo(String tenantId) { + protected Collection getForceTo(String tenantId) { String forceTo = null; if (tenantId != null && tenantId.length() > 0) { Map mailServers = CommandContextUtil.getProcessEngineConfiguration().getMailServers(); @@ -513,7 +541,7 @@ protected String getForceTo(String tenantId) { forceTo = CommandContextUtil.getProcessEngineConfiguration().getMailServerForceTo(); } - return forceTo; + return splitAndTrim(forceTo); } protected Charset getDefaultCharSet(String tenantId) { diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/mail/EmailServiceTaskTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/mail/EmailServiceTaskTest.java index b12335ace01..55e2566c6b4 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/mail/EmailServiceTaskTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/mail/EmailServiceTaskTest.java @@ -23,10 +23,12 @@ import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import javax.activation.DataHandler; import javax.mail.MessagingException; @@ -39,8 +41,13 @@ import org.flowable.engine.impl.test.HistoryTestHelper; import org.flowable.engine.test.Deployment; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.subethamail.wiser.WiserMessage; +import com.fasterxml.jackson.databind.ObjectMapper; + /** * @author Joram Barrez * @author Tim Stephenson @@ -56,11 +63,34 @@ public void testSimpleTextMail() throws Exception { assertThat(messages).hasSize(1); WiserMessage message = messages.get(0); - assertEmailSend(message, false, "Hello Kermit!", "This a text only e-mail.", "flowable@localhost", Collections.singletonList("kermit@activiti.org"), null); + assertEmailSend(message, false, "Hello Kermit!", "This a text only e-mail.", "flowable@localhost", Collections.singletonList("kermit@activiti.org"), + null); assertThat(message.getMimeMessage().getContentType()).isEqualTo("text/plain; charset=us-ascii"); assertProcessEnded(procId); } - + + @ParameterizedTest + @MethodSource(value = "recipientsTest") + @Deployment + public void testDynamicRecipients(Object recipients) throws MessagingException { + runtimeService.createProcessInstanceBuilder().processDefinitionKey("dynamicRecipients").variable("recipients", recipients).start(); + List messages = wiser.getMessages(); + assertThat(messages).hasSize(6); + WiserMessage message = messages.get(0); + MimeMessage mimeMessage = message.getMimeMessage(); + + assertThat(mimeMessage.getHeader("To", null)).isEqualTo("flowable@localhost, misspiggy@flowable.org"); + assertThat(mimeMessage.getHeader("Cc", null)).isEqualTo("flowable@localhost, misspiggy@flowable.org"); + } + + private static Stream recipientsTest() { + return Stream.of( + Arguments.of("flowable@localhost, misspiggy@flowable.org"), + Arguments.of(Arrays.asList("flowable@localhost", "misspiggy@flowable.org")), + Arguments.of(new ObjectMapper().createArrayNode().add("flowable@localhost").add("misspiggy@flowable.org")) + ); + } + @Test @Deployment(resources = "org/flowable/engine/test/bpmn/mail/EmailServiceTaskTest.testSimpleTextMail.bpmn20.xml") public void testSimpleTextMailCharset() throws Exception { diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/mail/EmailServiceTaskTest.testDynamicRecipients.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/mail/EmailServiceTaskTest.testDynamicRecipients.bpmn20.xml new file mode 100644 index 00000000000..444775ce26c --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/mail/EmailServiceTaskTest.testDynamicRecipients.bpmn20.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + Hello world + + + This is the content + + + ${recipients} + + + ${recipients} + + + ${recipients} + + + + + + + + + + \ No newline at end of file