diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/CmmnEngineConfiguration.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/CmmnEngineConfiguration.java index 99192355cb1..0e0d9d8a24c 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/CmmnEngineConfiguration.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/CmmnEngineConfiguration.java @@ -296,7 +296,9 @@ import org.flowable.job.service.impl.asyncexecutor.ExecuteAsyncRunnableFactory; import org.flowable.job.service.impl.asyncexecutor.FailedJobCommandFactory; import org.flowable.job.service.impl.asyncexecutor.JobManager; +import org.flowable.common.engine.impl.cfg.mail.DefaultMailClientProvider; import org.flowable.mail.common.api.client.FlowableMailClient; +import org.flowable.mail.common.api.client.MailClientProvider; import org.flowable.task.service.InternalTaskAssignmentManager; import org.flowable.task.service.InternalTaskVariableScopeResolver; import org.flowable.task.service.TaskPostProcessor; @@ -526,11 +528,10 @@ public class CmmnEngineConfiguration extends AbstractBuildableEngineConfiguratio protected HttpClientConfig httpClientConfig = new HttpClientConfig(); // Email - protected FlowableMailClient defaultMailClient; + protected MailClientProvider mailClientProvider = new DefaultMailClientProvider(); protected MailServerInfo defaultMailServer; protected String mailSessionJndi; protected Map mailServers = new HashMap<>(); - protected Map mailClients = new HashMap<>(); protected Map mailSessionsJndi = new HashMap<>(); // Async executor @@ -929,17 +930,23 @@ public void initExpressionManager() { } public void initMailClients() { - if (defaultMailClient == null) { + if (mailClientProvider == null) { + mailClientProvider = new DefaultMailClientProvider(); + } + if (!(mailClientProvider instanceof DefaultMailClientProvider defaultMailClientProvider)) { + return; // custom provider handles resolution at runtime + } + if (defaultMailClientProvider.getDefaultMailClient() == null) { String sessionJndi = getMailSessionJndi(); if (sessionJndi != null) { - defaultMailClient = FlowableMailClientCreator.createSessionClient(sessionJndi, getDefaultMailServer()); + defaultMailClientProvider.setDefaultMailClient(FlowableMailClientCreator.createSessionClient(sessionJndi, getDefaultMailServer())); } else { MailServerInfo mailServer = getDefaultMailServer(); String host = mailServer.getMailServerHost(); if (host == null) { throw new FlowableException("no SMTP host is configured for the default mail server"); } - defaultMailClient = FlowableMailClientCreator.createHostClient(host, mailServer); + defaultMailClientProvider.setDefaultMailClient(FlowableMailClientCreator.createHostClient(host, mailServer)); } } @@ -947,6 +954,7 @@ public void initMailClients() { tenantIds.addAll(mailServers.keySet()); if (!tenantIds.isEmpty()) { + Map mailClients = defaultMailClientProvider.getMailClients(); MailServerInfo defaultMailServer = getDefaultMailServer(); for (String tenantId : tenantIds) { if (mailClients.containsKey(tenantId)) { @@ -4078,12 +4086,34 @@ public CmmnEngineConfiguration setHttpClientConfig(HttpClientConfig httpClientCo return this; } + public MailClientProvider getMailClientProvider() { + return mailClientProvider; + } + + public CmmnEngineConfiguration setMailClientProvider(MailClientProvider mailClientProvider) { + this.mailClientProvider = mailClientProvider; + return this; + } + + /** + * @deprecated use {@link #getMailClientProvider()} and {@link MailClientProvider#getMailClient(String)} with tenantId {@code null} instead + */ + @Deprecated public FlowableMailClient getDefaultMailClient() { - return defaultMailClient; + return mailClientProvider.getMailClient(null); } + /** + * @deprecated use {@link #setMailClientProvider(MailClientProvider)} instead + */ + @Deprecated public CmmnEngineConfiguration setDefaultMailClient(FlowableMailClient defaultMailClient) { - this.defaultMailClient = defaultMailClient; + if (mailClientProvider instanceof DefaultMailClientProvider defaultProvider) { + defaultProvider.setDefaultMailClient(defaultMailClient); + } else { + throw new FlowableException("The mail client provider is not an instance of DefaultMailClientProvider. " + + "Use setMailClientProvider instead."); + } return this; } @@ -4219,17 +4249,37 @@ public MailServerInfo getMailServer(String tenantId) { return mailServers.get(tenantId); } + /** + * @deprecated use {@link #getMailClientProvider()} instead + */ + @Deprecated public Map getMailClients() { - return mailClients; + if (mailClientProvider instanceof DefaultMailClientProvider defaultProvider) { + return defaultProvider.getMailClients(); + } + return Collections.emptyMap(); } + /** + * @deprecated use {@link #setMailClientProvider(MailClientProvider)} instead + */ + @Deprecated public CmmnEngineConfiguration setMailClients(Map mailClients) { - this.mailClients = mailClients; + if (this.mailClientProvider instanceof DefaultMailClientProvider defaultProvider) { + defaultProvider.getMailClients().putAll(mailClients); + } else { + throw new FlowableException("The mail client provider is not an instance of DefaultMailClientProvider. " + + "Use setMailClientProvider instead."); + } return this; } + /** + * @deprecated use {@link #getMailClientProvider().getMailClient(String)} instead + */ + @Deprecated public FlowableMailClient getMailClient(String tenantId) { - return mailClients.get(tenantId); + return mailClientProvider.getMailClient(tenantId); } public Map getMailSessionsJndi() { diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/mail/CmmnMailActivityDelegate.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/mail/CmmnMailActivityDelegate.java index 5e648fed27c..5b223d3071d 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/mail/CmmnMailActivityDelegate.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/behavior/impl/mail/CmmnMailActivityDelegate.java @@ -12,7 +12,6 @@ */ package org.flowable.cmmn.engine.impl.behavior.impl.mail; -import org.apache.commons.lang3.StringUtils; import org.flowable.cmmn.api.delegate.DelegatePlanItemInstance; import org.flowable.cmmn.api.delegate.PlanItemJavaDelegate; import org.flowable.cmmn.engine.CmmnEngineConfiguration; @@ -35,17 +34,7 @@ public void execute(DelegatePlanItemInstance planItemInstance) { @Override protected FlowableMailClient getMailClient(DelegatePlanItemInstance planItemInstance) { CmmnEngineConfiguration cmmnEngineConfiguration = CommandContextUtil.getCmmnEngineConfiguration(); - String tenantId = planItemInstance.getTenantId(); - FlowableMailClient mailClient = null; - if (StringUtils.isNotBlank(tenantId)) { - mailClient = cmmnEngineConfiguration.getMailClient(tenantId); - } - - if (mailClient == null) { - mailClient = cmmnEngineConfiguration.getDefaultMailClient(); - } - - return mailClient; + return cmmnEngineConfiguration.getMailClientProvider().getMailClient(planItemInstance.getTenantId()); } @Override diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/task/CmmnEmailTestCase.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/task/CmmnEmailTestCase.java new file mode 100644 index 00000000000..223dd8d5c5f --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/task/CmmnEmailTestCase.java @@ -0,0 +1,97 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flowable.cmmn.test.task; + +import java.util.HashMap; +import java.util.Map; + +import org.flowable.cmmn.test.FlowableCmmnTestCase; +import org.flowable.common.engine.impl.cfg.mail.MailServerInfo; +import org.flowable.mail.common.api.client.MailClientProvider; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.subethamail.wiser.Wiser; + +/** + * @author Valentin Zickner + */ +@Tag("email") +public abstract class CmmnEmailTestCase extends FlowableCmmnTestCase { + + protected static Wiser wiser; + private MailClientProvider initialMailClientProvider; + private Map initialMailServers; + + @BeforeAll + public static void setupWiser() throws Exception { + int counter = 0; + boolean serverUpAndRunning = false; + while (!serverUpAndRunning && counter++ < 11) { + wiser = Wiser.port(5025); + try { + wiser.start(); + serverUpAndRunning = true; + } catch (RuntimeException e) { // Fix for slow port-closing Jenkins + if (e.getMessage().toLowerCase().contains("bindexception")) { + Thread.sleep(250L); + } + } + } + } + + @BeforeEach + public void setUp() { + wiser.getMessages().clear(); + initialMailClientProvider = cmmnEngineConfiguration.getMailClientProvider(); + reinitializeMailClients(); + Map mailServers = cmmnEngineConfiguration.getMailServers(); + initialMailServers = mailServers == null ? null : new HashMap<>(mailServers); + } + + @AfterEach + public void tearDown() { + if (initialMailServers != null) { + cmmnEngineConfiguration.getMailServers().clear(); + cmmnEngineConfiguration.getMailServers().putAll(initialMailServers); + } + cmmnEngineConfiguration.setMailClientProvider(initialMailClientProvider); + } + + @AfterAll + public static void stopWiser() { + wiser.stop(); + } + + protected void reinitializeMailClients() { + cmmnEngineConfiguration.setMailClientProvider(null); + cmmnEngineConfiguration.initMailClients(); + } + + protected void addMailServer(String tenantId, String defaultFrom, String forceTo) { + MailServerInfo mailServerInfo = new MailServerInfo(); + mailServerInfo.setMailServerHost("localhost"); + mailServerInfo.setMailServerPort(5025); + mailServerInfo.setMailServerUseSSL(false); + mailServerInfo.setMailServerUseTLS(false); + mailServerInfo.setMailServerDefaultFrom(defaultFrom); + mailServerInfo.setMailServerForceTo(forceTo); + mailServerInfo.setMailServerUsername(defaultFrom); + mailServerInfo.setMailServerPassword("password"); + + cmmnEngineConfiguration.getMailServers().put(tenantId, mailServerInfo); + } + +} 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 89dc3248071..668bfb74d33 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 @@ -33,15 +33,9 @@ import org.apache.commons.lang3.Validate; import org.flowable.cmmn.engine.test.CmmnDeployment; -import org.flowable.cmmn.test.FlowableCmmnTestCase; import org.flowable.common.engine.impl.cfg.mail.FlowableMailClientCreator; import org.flowable.common.engine.impl.cfg.mail.MailServerInfo; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.subethamail.wiser.Wiser; import org.subethamail.wiser.WiserMessage; import tools.jackson.databind.node.ArrayNode; @@ -49,41 +43,7 @@ /** * @author Joram Barrez */ -@Tag("email") -public class CmmnMailTaskTest extends FlowableCmmnTestCase { - - protected static Wiser wiser; - - @BeforeAll - public static void setupWiser() throws Exception { - wiser = Wiser.port(5025); - - int counter = 0; - boolean serverUpAndRunning = false; - while (!serverUpAndRunning && counter++ < 11) { - - wiser = Wiser.port(5025); - - try { - wiser.start(); - serverUpAndRunning = true; - } catch (RuntimeException e) { // Fix for slow port-closing Jenkins - if (e.getMessage().toLowerCase().contains("bindexception")) { - Thread.sleep(250L); - } - } - } - } - - @BeforeEach - public void resetMessages() { - wiser.getMessages().clear(); - } - - @AfterAll - public static void stopWiser() { - wiser.stop(); - } +public class CmmnMailTaskTest extends CmmnEmailTestCase { @Test @CmmnDeployment diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/task/CmmnMailTaskWithMailClientProviderTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/task/CmmnMailTaskWithMailClientProviderTest.java new file mode 100644 index 00000000000..c534f493d36 --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/task/CmmnMailTaskWithMailClientProviderTest.java @@ -0,0 +1,133 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flowable.cmmn.test.task; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.flowable.cmmn.engine.test.CmmnDeployment; +import org.flowable.common.engine.impl.cfg.mail.DefaultMailClientProvider; +import org.flowable.common.engine.impl.cfg.mail.FlowableMailClientCreator; +import org.flowable.common.engine.impl.cfg.mail.MailServerInfo; +import org.flowable.mail.common.api.client.FlowableMailClient; +import org.flowable.mail.common.api.client.MailClientProvider; +import org.junit.jupiter.api.Test; +import org.subethamail.wiser.WiserMessage; + +/** + * Tests for the {@link MailClientProvider} integration in the CMMN engine. + * + * @author Valentin Zickner + */ +class CmmnMailTaskWithMailClientProviderTest extends CmmnEmailTestCase { + + @Test + @CmmnDeployment(resources = "org/flowable/cmmn/test/task/CmmnMailTaskWithMailClientProviderTest.testSimpleTextMail.cmmn") + void testProviderReturnsClientForEmptyTenantId() { + FlowableMailClient providerClient = createMailClient("provider-default@flowable.org"); + cmmnEngineConfiguration.setMailClientProvider(requestedTenantId -> { + if (StringUtils.isEmpty(requestedTenantId)) { + return providerClient; + } + return null; + }); + + cmmnRuntimeService.createCaseInstanceBuilder() + .caseDefinitionKey("testSimpleTextMail") + .start(); + + List messages = wiser.getMessages(); + assertThat(messages).hasSize(1); + assertThat(messages.get(0).getEnvelopeSender()).isEqualTo("provider-default@flowable.org"); + } + + @Test + @CmmnDeployment(resources = "org/flowable/cmmn/test/task/CmmnMailTaskWithMailClientProviderTest.testSimpleTextMail.cmmn", tenantId = "providerTenant") + void testProviderReturnsClientForTenant() { + + FlowableMailClient tenantClient = createMailClient("provider-tenant@flowable.org"); + cmmnEngineConfiguration.setMailClientProvider(requestedTenantId -> { + if ("providerTenant".equals(requestedTenantId)) { + return tenantClient; + } + return null; + }); + + cmmnRuntimeService.createCaseInstanceBuilder() + .caseDefinitionKey("testSimpleTextMail") + .tenantId("providerTenant") + .start(); + + List messages = wiser.getMessages(); + assertThat(messages).hasSize(1); + assertThat(messages.get(0).getEnvelopeSender()).isEqualTo("provider-tenant@flowable.org"); + } + + @Test + @CmmnDeployment(resources = "org/flowable/cmmn/test/task/CmmnMailTaskWithMailClientProviderTest.testSimpleTextMail.cmmn") + public void testDefaultProviderFallsBackToDefaultClient() { + cmmnRuntimeService.createCaseInstanceBuilder() + .caseDefinitionKey("testSimpleTextMail") + .start(); + + List messages = wiser.getMessages(); + assertThat(messages).hasSize(1); + // DefaultMailClientProvider should resolve the default mail client + assertThat(messages.get(0).getEnvelopeSender()).isEqualTo("flowable@localhost"); + } + + @Test + @CmmnDeployment(resources = "org/flowable/cmmn/test/task/CmmnMailTaskWithMailClientProviderTest.testSimpleTextMail.cmmn", tenantId = "staticTenant") + public void testDefaultProviderResolvesTenantFromStaticConfig() { + addMailServer("staticTenant", "static-tenant@flowable.org", null); + reinitializeMailClients(); + + cmmnRuntimeService.createCaseInstanceBuilder() + .caseDefinitionKey("testSimpleTextMail") + .tenantId("staticTenant") + .start(); + + List messages = wiser.getMessages(); + assertThat(messages).hasSize(1); + assertThat(messages.get(0).getEnvelopeSender()).isEqualTo("static-tenant@flowable.org"); + } + + @Test + @CmmnDeployment(resources = "org/flowable/cmmn/test/task/CmmnMailTaskWithMailClientProviderTest.testSimpleTextMail.cmmn") + public void testDefaultMailClientProviderIsSetByDefault() { + assertThat(cmmnEngineConfiguration.getMailClientProvider()).isInstanceOf(DefaultMailClientProvider.class); + + cmmnRuntimeService.createCaseInstanceBuilder() + .caseDefinitionKey("testSimpleTextMail") + .start(); + + List messages = wiser.getMessages(); + assertThat(messages).hasSize(1); + assertThat(messages.get(0).getEnvelopeSender()).isEqualTo("flowable@localhost"); + } + + protected FlowableMailClient createMailClient(String defaultFrom) { + MailServerInfo mailServerInfo = new MailServerInfo(); + mailServerInfo.setMailServerHost("localhost"); + mailServerInfo.setMailServerPort(5025); + mailServerInfo.setMailServerUseSSL(false); + mailServerInfo.setMailServerUseTLS(false); + mailServerInfo.setMailServerDefaultFrom(defaultFrom); + mailServerInfo.setMailServerUsername(defaultFrom); + mailServerInfo.setMailServerPassword("password"); + return FlowableMailClientCreator.createHostClient("localhost", mailServerInfo); + } + +} diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/task/CmmnMailTaskWithMailClientProviderTest.testSimpleTextMail.cmmn b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/task/CmmnMailTaskWithMailClientProviderTest.testSimpleTextMail.cmmn new file mode 100644 index 00000000000..62a505ddc41 --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/task/CmmnMailTaskWithMailClientProviderTest.testSimpleTextMail.cmmn @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/cfg/mail/DefaultMailClientProvider.java b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/cfg/mail/DefaultMailClientProvider.java new file mode 100644 index 00000000000..ff86a0577f8 --- /dev/null +++ b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/cfg/mail/DefaultMailClientProvider.java @@ -0,0 +1,60 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flowable.common.engine.impl.cfg.mail; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.flowable.mail.common.api.client.FlowableMailClient; +import org.flowable.mail.common.api.client.MailClientProvider; + +/** + * Default implementation of {@link MailClientProvider} that resolves mail clients from + * a static map keyed by tenant identifier, with a fallback to a default mail client. + * + * @author Valentin Zickner + */ +public class DefaultMailClientProvider implements MailClientProvider { + + protected FlowableMailClient defaultMailClient; + protected final Map tenantMailClients; + + public DefaultMailClientProvider() { + this.tenantMailClients = new HashMap<>(); + } + + @Override + public FlowableMailClient getMailClient(String tenantId) { + if (StringUtils.isNotBlank(tenantId)) { + FlowableMailClient client = tenantMailClients.get(tenantId); + if (client != null) { + return client; + } + } + return defaultMailClient; + } + + public FlowableMailClient getDefaultMailClient() { + return defaultMailClient; + } + + public void setDefaultMailClient(FlowableMailClient defaultMailClient) { + this.defaultMailClient = defaultMailClient; + } + + public Map getMailClients() { + return tenantMailClients; + } + +} diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/ProcessEngineConfiguration.java b/modules/flowable-engine/src/main/java/org/flowable/engine/ProcessEngineConfiguration.java index 40752fd6af8..d11c7fe355a 100755 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/ProcessEngineConfiguration.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/ProcessEngineConfiguration.java @@ -16,11 +16,13 @@ import java.io.InputStream; import java.nio.charset.Charset; import java.time.Duration; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import javax.sql.DataSource; +import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.async.AsyncTaskExecutor; import org.flowable.common.engine.api.async.AsyncTaskInvoker; import org.flowable.common.engine.impl.AbstractBuildableEngineConfiguration; @@ -33,7 +35,9 @@ import org.flowable.engine.impl.cfg.StandaloneProcessEngineConfiguration; import org.flowable.image.ProcessDiagramGenerator; import org.flowable.job.service.impl.asyncexecutor.AsyncExecutor; +import org.flowable.common.engine.impl.cfg.mail.DefaultMailClientProvider; import org.flowable.mail.common.api.client.FlowableMailClient; +import org.flowable.mail.common.api.client.MailClientProvider; import org.flowable.task.service.TaskPostProcessor; /** @@ -89,11 +93,10 @@ public abstract class ProcessEngineConfiguration extends AbstractBuildableEngine protected boolean asyncExecutorActivate; protected boolean asyncHistoryExecutorActivate; - protected FlowableMailClient defaultMailClient; + protected MailClientProvider mailClientProvider = new DefaultMailClientProvider(); protected MailServerInfo defaultMailServer; protected String mailSessionJndi; protected Map mailServers = new HashMap<>(); - protected Map mailClients = new HashMap<>(); protected Map mailSessionsJndi = new HashMap<>(); // Set Http Client config defaults @@ -245,12 +248,34 @@ public ProcessEngineConfiguration setHistory(String history) { return this; } + public MailClientProvider getMailClientProvider() { + return mailClientProvider; + } + + public ProcessEngineConfiguration setMailClientProvider(MailClientProvider mailClientProvider) { + this.mailClientProvider = mailClientProvider; + return this; + } + + /** + * @deprecated use {@link #getMailClientProvider()} and {@link MailClientProvider#getMailClient(String)} with {@code null} instead + */ + @Deprecated public FlowableMailClient getDefaultMailClient() { - return defaultMailClient; + return mailClientProvider.getMailClient(null); } + /** + * @deprecated use {@link #setMailClientProvider(MailClientProvider)} instead + */ + @Deprecated public ProcessEngineConfiguration setDefaultMailClient(FlowableMailClient defaultMailClient) { - this.defaultMailClient = defaultMailClient; + if (mailClientProvider instanceof DefaultMailClientProvider defaultProvider) { + defaultProvider.setDefaultMailClient(defaultMailClient); + } else { + throw new FlowableException("The mail client provider is not an instance of DefaultMailClientProvider. " + + "Use setMailClientProvider instead."); + } return this; } @@ -386,16 +411,36 @@ public ProcessEngineConfiguration setMailServers(Map mai return this; } + /** + * @deprecated use {@link #getMailClientProvider().getMailClient(String)} instead + */ + @Deprecated public FlowableMailClient getMailClient(String tenantId) { - return mailClients.get(tenantId); + return mailClientProvider.getMailClient(tenantId); } + /** + * @deprecated use {@link #getMailClientProvider()} instead + */ + @Deprecated public Map getMailClients() { - return mailClients; + if (mailClientProvider instanceof DefaultMailClientProvider defaultProvider) { + return defaultProvider.getMailClients(); + } + return Collections.emptyMap(); } + /** + * @deprecated use {@link #setMailClientProvider(MailClientProvider)} instead + */ + @Deprecated public ProcessEngineConfiguration setMailClients(Map mailClients) { - this.mailClients.putAll(mailClients); + if (this.mailClientProvider instanceof DefaultMailClientProvider defaultProvider) { + defaultProvider.getMailClients().putAll(mailClients); + } else { + throw new FlowableException("The mail client provider is not an instance of DefaultMailClientProvider. " + + "Use setMailClientProvider instead."); + } return this; } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/mail/BpmnMailActivityDelegate.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/mail/BpmnMailActivityDelegate.java index ba8e767d288..f358322a417 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/mail/BpmnMailActivityDelegate.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/mail/BpmnMailActivityDelegate.java @@ -12,7 +12,6 @@ */ package org.flowable.engine.impl.bpmn.mail; -import org.apache.commons.lang3.StringUtils; import org.flowable.common.engine.api.delegate.Expression; import org.flowable.common.engine.impl.mail.BaseMailActivityDelegate; import org.flowable.content.api.ContentService; @@ -36,17 +35,7 @@ public void execute(DelegateExecution execution) { @Override protected FlowableMailClient getMailClient(DelegateExecution execution) { ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration(); - String tenantId = execution.getTenantId(); - FlowableMailClient mailClient = null; - if (StringUtils.isNotBlank(tenantId)) { - mailClient = processEngineConfiguration.getMailClient(tenantId); - } - - if (mailClient == null) { - mailClient = processEngineConfiguration.getDefaultMailClient(); - } - - return mailClient; + return processEngineConfiguration.getMailClientProvider().getMailClient(execution.getTenantId()); } @Override diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cfg/ProcessEngineConfigurationImpl.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cfg/ProcessEngineConfigurationImpl.java index 935cf749e2e..c883c412c96 100755 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cfg/ProcessEngineConfigurationImpl.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cfg/ProcessEngineConfigurationImpl.java @@ -389,6 +389,8 @@ import org.flowable.job.service.impl.asyncexecutor.ExecuteAsyncRunnableFactory; import org.flowable.job.service.impl.asyncexecutor.FailedJobCommandFactory; import org.flowable.job.service.impl.asyncexecutor.JobManager; +import org.flowable.common.engine.impl.cfg.mail.DefaultMailClientProvider; +import org.flowable.mail.common.api.client.FlowableMailClient; import org.flowable.task.api.TaskQueryInterceptor; import org.flowable.task.api.history.HistoricTaskQueryInterceptor; import org.flowable.task.service.InternalTaskAssignmentManager; @@ -2352,17 +2354,23 @@ public void initExpressionManager() { } public void initMailClients() { - if (defaultMailClient == null) { + if (mailClientProvider == null) { + mailClientProvider = new DefaultMailClientProvider(); + } + if (!(mailClientProvider instanceof DefaultMailClientProvider defaultMailClientProvider)) { + return; // custom provider handles resolution at runtime + } + if (defaultMailClientProvider.getDefaultMailClient() == null) { String sessionJndi = getMailSessionJndi(); if (sessionJndi != null) { - defaultMailClient = FlowableMailClientCreator.createSessionClient(sessionJndi, getDefaultMailServer()); + defaultMailClientProvider.setDefaultMailClient(FlowableMailClientCreator.createSessionClient(sessionJndi, getDefaultMailServer())); } else { MailServerInfo mailServer = getDefaultMailServer(); String host = mailServer.getMailServerHost(); if (host == null) { throw new FlowableException("no SMTP host is configured for the default mail server"); } - defaultMailClient = FlowableMailClientCreator.createHostClient(host, mailServer); + defaultMailClientProvider.setDefaultMailClient(FlowableMailClientCreator.createHostClient(host, mailServer)); } } @@ -2370,6 +2378,7 @@ public void initMailClients() { tenantIds.addAll(mailServers.keySet()); if (!tenantIds.isEmpty()) { + Map mailClients = defaultMailClientProvider.getMailClients(); MailServerInfo defaultMailServer = getDefaultMailServer(); for (String tenantId : tenantIds) { if (mailClients.containsKey(tenantId)) { diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/mail/EmailSendTaskWithMailClientProviderTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/mail/EmailSendTaskWithMailClientProviderTest.java new file mode 100644 index 00000000000..53d9bf5aead --- /dev/null +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/mail/EmailSendTaskWithMailClientProviderTest.java @@ -0,0 +1,135 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flowable.engine.test.bpmn.mail; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import org.drools.util.StringUtils; +import org.flowable.common.engine.impl.cfg.mail.DefaultMailClientProvider; +import org.flowable.common.engine.impl.cfg.mail.FlowableMailClientCreator; +import org.flowable.common.engine.impl.cfg.mail.MailServerInfo; +import org.flowable.engine.test.Deployment; +import org.flowable.mail.common.api.client.FlowableMailClient; +import org.flowable.mail.common.api.client.MailClientProvider; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.subethamail.wiser.WiserMessage; + +/** + * Tests for the {@link MailClientProvider} integration in the BPMN engine. + * + * @author Valentin Zickner + */ +class EmailSendTaskWithMailClientProviderTest extends EmailTestCase { + + protected MailClientProvider initialMailClientProvider; + + @BeforeEach + void saveProvider() { + initialMailClientProvider = processEngineConfiguration.getMailClientProvider(); + reinitilizeMailClients(); + } + + @AfterEach + void restoreProvider() { + processEngineConfiguration.setMailClientProvider(initialMailClientProvider); + } + + @Test + @Deployment(resources = "org/flowable/engine/test/bpmn/mail/EmailSendTaskTest.testSimpleTextMail.bpmn20.xml", tenantId = "providerTenant") + void testProviderReturnsClientForTenant() { + FlowableMailClient tenantClient = createMailClient("provider-tenant@flowable.org"); + processEngineConfiguration.setMailClientProvider(requestedTenantId -> { + if ("providerTenant".equals(requestedTenantId)) { + return tenantClient; + } + return null; + }); + + runtimeService.startProcessInstanceByKeyAndTenantId("simpleTextOnly", "providerTenant"); + + List messages = wiser.getMessages(); + assertThat(messages).hasSize(1); + assertThat(messages.get(0).getEnvelopeSender()).isEqualTo("provider-tenant@flowable.org"); + } + + @Test + @Deployment(resources = "org/flowable/engine/test/bpmn/mail/EmailSendTaskTest.testSimpleTextMail.bpmn20.xml", tenantId = "staticTenant") + void testDefaultProviderResolvesTenantFromStaticConfig() { + addMailServer("staticTenant", "static-tenant@flowable.org", null); + reinitilizeMailClients(); + + runtimeService.startProcessInstanceByKeyAndTenantId("simpleTextOnly", "staticTenant"); + + List messages = wiser.getMessages(); + assertThat(messages).hasSize(1); + assertThat(messages.get(0).getEnvelopeSender()).isEqualTo("static-tenant@flowable.org"); + } + + @Test + @Deployment(resources = "org/flowable/engine/test/bpmn/mail/EmailSendTaskTest.testSimpleTextMail.bpmn20.xml") + void testProviderReturnsClientForEmptyTenantId() { + FlowableMailClient defaultProviderClient = createMailClient("provider-default@flowable.org"); + processEngineConfiguration.setMailClientProvider(requestedTenantId -> { + if (StringUtils.isEmpty(requestedTenantId)) { + return defaultProviderClient; + } + return null; + }); + + runtimeService.startProcessInstanceByKey("simpleTextOnly"); + + List messages = wiser.getMessages(); + assertThat(messages).hasSize(1); + assertThat(messages.get(0).getEnvelopeSender()).isEqualTo("provider-default@flowable.org"); + } + + @Test + @Deployment(resources = "org/flowable/engine/test/bpmn/mail/EmailSendTaskTest.testSimpleTextMail.bpmn20.xml") + void testDefaultProviderFallsBackToDefaultClient() { + runtimeService.startProcessInstanceByKey("simpleTextOnly"); + + List messages = wiser.getMessages(); + assertThat(messages).hasSize(1); + // DefaultMailClientProvider should resolve the default mail client (flowable@localhost from test config) + assertThat(messages.get(0).getEnvelopeSender()).isEqualTo("flowable@localhost"); + } + + @Test + @Deployment(resources = "org/flowable/engine/test/bpmn/mail/EmailSendTaskTest.testSimpleTextMail.bpmn20.xml") + void testDefaultMailClientProviderIsSetByDefault() { + assertThat(processEngineConfiguration.getMailClientProvider()).isInstanceOf(DefaultMailClientProvider.class); + + runtimeService.startProcessInstanceByKey("simpleTextOnly"); + + List messages = wiser.getMessages(); + assertThat(messages).hasSize(1); + assertThat(messages.get(0).getEnvelopeSender()).isEqualTo("flowable@localhost"); + } + + protected FlowableMailClient createMailClient(String defaultFrom) { + MailServerInfo mailServerInfo = new MailServerInfo(); + mailServerInfo.setMailServerHost("localhost"); + mailServerInfo.setMailServerPort(5025); + mailServerInfo.setMailServerUseSSL(false); + mailServerInfo.setMailServerUseTLS(false); + mailServerInfo.setMailServerDefaultFrom(defaultFrom); + mailServerInfo.setMailServerUsername(defaultFrom); + mailServerInfo.setMailServerPassword("password"); + return FlowableMailClientCreator.createHostClient("localhost", mailServerInfo); + } + +} diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/mail/EmailTestCase.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/mail/EmailTestCase.java index 8c49f175ed8..fdb8aac573c 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/mail/EmailTestCase.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/mail/EmailTestCase.java @@ -18,6 +18,7 @@ import org.flowable.common.engine.impl.cfg.mail.MailServerInfo; import org.flowable.engine.impl.test.PluggableFlowableTestCase; +import org.flowable.mail.common.api.client.MailClientProvider; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; @@ -31,12 +32,15 @@ public abstract class EmailTestCase extends PluggableFlowableTestCase { protected Wiser wiser; private String initialForceTo; + private MailClientProvider initialMailClientProvider; private Map initialMailServers; @BeforeEach protected void setUp() throws Exception { initialForceTo = processEngineConfiguration.getMailServerForceTo(); + initialMailClientProvider = processEngineConfiguration.getMailClientProvider(); + reinitilizeMailClients(); Map mailServers = processEngineConfiguration.getMailServers(); initialMailServers = mailServers == null ? null : new HashMap<>(mailServers); boolean serverUpAndRunning = false; @@ -63,12 +67,11 @@ protected void tearDown() throws Exception { processEngineConfiguration.setMailServerForceTo(initialForceTo); processEngineConfiguration.setMailServers(initialMailServers); - reinitilizeMailClients(); + processEngineConfiguration.setMailClientProvider(initialMailClientProvider); } protected void reinitilizeMailClients() { - processEngineConfiguration.setDefaultMailClient(null); - processEngineConfiguration.getMailClients().clear(); + processEngineConfiguration.setMailClientProvider(null); processEngineConfiguration.initMailClients(); } diff --git a/modules/flowable-mail/src/main/java/org/flowable/mail/common/api/client/MailClientProvider.java b/modules/flowable-mail/src/main/java/org/flowable/mail/common/api/client/MailClientProvider.java new file mode 100644 index 00000000000..e504c9add92 --- /dev/null +++ b/modules/flowable-mail/src/main/java/org/flowable/mail/common/api/client/MailClientProvider.java @@ -0,0 +1,35 @@ +/* Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.flowable.mail.common.api.client; + +/** + * Provider for dynamically resolving {@link FlowableMailClient} instances. + *

+ * This allows mail client resolution to be dynamic rather than statically configured at engine startup. + * For example, this can be used to support dynamically added tenants or configuration changes at runtime. + *

+ * Implementations must be thread-safe, as this provider may be called concurrently from multiple threads. + * + * @author Valentin Zickner + */ +@FunctionalInterface +public interface MailClientProvider { + + /** + * Returns the mail client for the given tenant identifier. + * + * @param tenantId the tenant identifier, or {@code null} for the default (non-tenant) case + * @return the mail client for the given tenant + */ + FlowableMailClient getMailClient(String tenantId); +}