From 53a6a585812ca3860c6eb8dda177c0db17d45a4e Mon Sep 17 00:00:00 2001 From: Nour AL KOTOB Date: Wed, 27 Sep 2023 19:12:55 +0200 Subject: [PATCH 1/3] NXP-32029: Add a new MailService and SMTP implementation --- .../automation/core/mail/BlobDataSource.java | 2 + .../ecm/automation/core/mail/Composer.java | 5 + .../ecm/automation/core/mail/Mailer.java | 58 +++- .../operations/notification/SendMail.java | 141 +++++---- .../nuxeo-automation-test/pom.xml | 6 + .../ecm/automation/core/SendMailTest.java | 4 +- .../test/EmbeddedAutomationClientTest.java | 16 +- .../resources/bad-mail-sender-contrib.xml | 12 + .../java/org/nuxeo/easyshare/EasyShare.java | 8 +- .../user/invite/UserInvitationComponent.java | 27 +- modules/platform/nuxeo-mail/pom.xml | 5 +- .../java/org/nuxeo/mail/BlobDataSource.java | 59 ++++ .../org/nuxeo/mail/JndiSMTPMailSender.java | 38 +++ .../java/org/nuxeo/mail/MailException.java | 42 +++ .../main/java/org/nuxeo/mail/MailMessage.java | 286 ++++++++++++++++++ .../main/java/org/nuxeo/mail/MailSender.java | 28 ++ .../org/nuxeo/mail/MailSenderDescriptor.java | 61 ++++ .../main/java/org/nuxeo/mail/MailService.java | 29 ++ .../java/org/nuxeo/mail/MailServiceImpl.java | 88 ++++++ .../org/nuxeo/mail/MailSessionBuilder.java | 9 + .../java/org/nuxeo/mail/SMTPMailSender.java | 124 ++++++++ .../src/main/resources/META-INF/MANIFEST.MF | 1 + .../main/resources/OSGI-INF/mail-service.xml | 31 ++ .../org/nuxeo/mail/SmtpMailServerFeature.java | 123 +------- .../org/nuxeo/mail/TestSMTPMailSender.java | 59 ++++ .../nuxeo/mail/TestSmtpMailServerFeature.java | 22 +- .../src/test/resources/META-INF/MANIFEST.MF | 2 +- .../test-smtp-mail-sender-contrib.xml | 13 + .../nuxeo-platform-comment/pom.xml | 6 + .../comment/CommentNotificationFeature.java | 5 +- .../comment/TestCommentsMigrator.java | 4 +- .../OSGI-INF/notification-contrib.xml | 11 - .../nuxeo-platform-notification/pom.xml | 21 ++ .../NotificationEventListener.java | 15 +- .../ec/notification/email/EmailHelper.java | 127 ++++---- .../service/GeneralSettingsDescriptor.java | 16 + .../service/NotificationService.java | 42 ++- .../notification/api/NotificationManager.java | 9 + .../ec/notification/NotificationFeature.java | 31 ++ .../notification/TestEmailNotification.java | 5 +- .../notification/TestNotificationManager.java | 10 +- .../TestRegisterNotificationService.java | 35 +-- .../ec/notification/TestRenderingService.java | 3 +- .../UserSubscriptionAdapterTest.java | 4 +- .../SubscribeAndUnsubscribeTest.java | 4 +- .../notification-event-listener-contrib.xml | 9 - .../default-general-settings-contrib.xml | 12 + .../notification-contrib-disabled.xml | 9 - .../notification-contrib-overridden.xml | 2 +- .../test/resources/notification-contrib.xml | 9 - .../test/resources/notifications.properties | 3 - pom.xml | 6 + .../default-smtp-sender-config.xml.nxftl | 31 ++ 53 files changed, 1336 insertions(+), 392 deletions(-) create mode 100644 modules/platform/nuxeo-automation/nuxeo-automation-test/src/test/resources/bad-mail-sender-contrib.xml create mode 100644 modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/BlobDataSource.java create mode 100644 modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/JndiSMTPMailSender.java create mode 100644 modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailException.java create mode 100644 modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailMessage.java create mode 100644 modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailSender.java create mode 100644 modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailSenderDescriptor.java create mode 100644 modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailService.java create mode 100644 modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailServiceImpl.java create mode 100644 modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/SMTPMailSender.java create mode 100644 modules/platform/nuxeo-mail/src/main/resources/OSGI-INF/mail-service.xml create mode 100644 modules/platform/nuxeo-mail/src/test/java/org/nuxeo/mail/TestSMTPMailSender.java create mode 100644 modules/platform/nuxeo-mail/src/test/resources/OSGI-INF/test-smtp-mail-sender-contrib.xml delete mode 100644 modules/platform/nuxeo-platform-comment/nuxeo-platform-comment/src/test/resources/OSGI-INF/notification-contrib.xml create mode 100644 modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/NotificationFeature.java create mode 100644 modules/platform/nuxeo-platform-notification/src/test/resources/default-general-settings-contrib.xml delete mode 100644 modules/platform/nuxeo-platform-notification/src/test/resources/notifications.properties create mode 100644 server/nuxeo-nxr-server/src/main/resources/templates/common/config/default-smtp-sender-config.xml.nxftl diff --git a/modules/platform/nuxeo-automation/nuxeo-automation-core/src/main/java/org/nuxeo/ecm/automation/core/mail/BlobDataSource.java b/modules/platform/nuxeo-automation/nuxeo-automation-core/src/main/java/org/nuxeo/ecm/automation/core/mail/BlobDataSource.java index ebaff3ddcea..995296d1084 100644 --- a/modules/platform/nuxeo-automation/nuxeo-automation-core/src/main/java/org/nuxeo/ecm/automation/core/mail/BlobDataSource.java +++ b/modules/platform/nuxeo-automation/nuxeo-automation-core/src/main/java/org/nuxeo/ecm/automation/core/mail/BlobDataSource.java @@ -27,8 +27,10 @@ import javax.activation.DataSource; import org.nuxeo.ecm.core.api.Blob; /** + * @deprecated since 2023.4 Use {@link org.nuxeo.mail.BlobDataSource} instead * @author Bogdan Stefanescu */ +@Deprecated(since = "2023.4") public class BlobDataSource implements DataSource { protected final Blob blob; diff --git a/modules/platform/nuxeo-automation/nuxeo-automation-core/src/main/java/org/nuxeo/ecm/automation/core/mail/Composer.java b/modules/platform/nuxeo-automation/nuxeo-automation-core/src/main/java/org/nuxeo/ecm/automation/core/mail/Composer.java index 9aa11f53f11..2ecb572b4d6 100644 --- a/modules/platform/nuxeo-automation/nuxeo-automation-core/src/main/java/org/nuxeo/ecm/automation/core/mail/Composer.java +++ b/modules/platform/nuxeo-automation/nuxeo-automation-core/src/main/java/org/nuxeo/ecm/automation/core/mail/Composer.java @@ -45,6 +45,7 @@ import org.nuxeo.ecm.core.api.Blob; import org.nuxeo.ecm.platform.rendering.api.RenderingException; import org.nuxeo.ecm.platform.rendering.api.ResourceLocator; import org.nuxeo.ecm.platform.rendering.fm.FreemarkerEngine; +import org.nuxeo.mail.BlobDataSource; import org.nuxeo.runtime.api.Framework; import freemarker.core.Environment; @@ -52,8 +53,11 @@ import freemarker.template.Template; import freemarker.template.TemplateException; /** + * @deprecated since 2023.4 The {@link org.nuxeo.mail.MailService} takes {@link org.nuxeo.mail.MailMessage} which can be + * easily composed via its {@link org.nuxeo.mail.MailMessage.Builder}. * @author Bogdan Stefanescu */ +@Deprecated(since = "2023.4") public class Composer { private static final Log log = LogFactory.getLog(Composer.class); @@ -133,6 +137,7 @@ public class Composer { if (env != null) { File file = new File(env.getConfig(), "mail.properties"); if (file.isFile()) { + log.warn("mail properties should be directly placed in nuxeo.conf or in a MailSender contribution"); return file; } } diff --git a/modules/platform/nuxeo-automation/nuxeo-automation-core/src/main/java/org/nuxeo/ecm/automation/core/mail/Mailer.java b/modules/platform/nuxeo-automation/nuxeo-automation-core/src/main/java/org/nuxeo/ecm/automation/core/mail/Mailer.java index 12539a25295..b3bac971602 100644 --- a/modules/platform/nuxeo-automation/nuxeo-automation-core/src/main/java/org/nuxeo/ecm/automation/core/mail/Mailer.java +++ b/modules/platform/nuxeo-automation/nuxeo-automation-core/src/main/java/org/nuxeo/ecm/automation/core/mail/Mailer.java @@ -20,7 +20,10 @@ package org.nuxeo.ecm.automation.core.mail; import java.io.IOException; import java.io.InputStream; +import java.util.Arrays; +import java.util.List; import java.util.Properties; +import java.util.stream.Collectors; import javax.mail.Address; import javax.mail.Authenticator; @@ -31,7 +34,13 @@ import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; +import org.nuxeo.ecm.core.api.NuxeoException; +import org.nuxeo.mail.MailException; +import org.nuxeo.mail.MailMessage; +import org.nuxeo.mail.MailSender; +import org.nuxeo.mail.MailService; import org.nuxeo.mail.MailSessionBuilder; +import org.nuxeo.runtime.api.Framework; import static org.nuxeo.mail.MailConstants.CONFIGURATION_MAIL_DEBUG; import static org.nuxeo.mail.MailConstants.CONFIGURATION_MAIL_SMTP_AUTH; @@ -41,8 +50,10 @@ import static org.nuxeo.mail.MailConstants.CONFIGURATION_MAIL_SMTP_PORT; import static org.nuxeo.mail.MailConstants.CONFIGURATION_MAIL_SMTP_USER; /** + * @deprecated since 2023.4 Use a {@link MailSender} instead * @author Bogdan Stefanescu */ +@Deprecated(since = "2023.4") public class Mailer { protected Properties config; @@ -216,14 +227,8 @@ public class Mailer { // Here, no Authenticator argument is used (it is null). // Authenticators are used to prompt the user for user // name and password. - MimeMessage message = new MimeMessage(getSession()); - // the "from" address may be set in code, or set in the - // config file under "mail.from" ; here, the latter style is used - message.setFrom(new InternetAddress(from)); - message.addRecipient(javax.mail.Message.RecipientType.TO, new InternetAddress(to)); - message.setSubject(subject); - message.setText(body); - Transport.send(message); + var message = new MailMessage.Builder(to).from(from).subject(subject).content(body).build(); + Framework.getService(MailService.class).sendMail(message); } public static class Message extends MimeMessage { @@ -299,7 +304,42 @@ public class Mailer { } public void send() throws MessagingException { - Transport.send(this); + Framework.getService(MailService.class).sendMail(fromMimeMessage(this)); + } + + protected MailMessage fromMimeMessage(MimeMessage original) { + try { + // get subject details + String subject = null; + String subjectCharset = null; + var subjectHeader = original.getSubject(); + if (subjectHeader != null) { + var details = subjectHeader.split(";"); + subject = details[0]; + if (details.length > 1) { + subjectCharset = details[1]; + } + } + + List mimeTos = asStringList(original.getRecipients(MimeMessage.RecipientType.TO)); + return new MailMessage.Builder(mimeTos).from(asStringList(original.getFrom())) + .cc(asStringList( + original.getRecipients(MimeMessage.RecipientType.CC))) + .bcc(asStringList( + original.getRecipients(MimeMessage.RecipientType.BCC))) + .replyTo(asStringList(original.getReplyTo())) + .date(original.getSentDate()) + .subject(subject, subjectCharset) + .content(original.getContent(), original.getContentType()) + .build(); + } catch (IOException | MessagingException e) { + throw new MailException("Could not read mail content", e); + } + } + + protected List asStringList(Object[] objects) { + return objects == null ? List.of() + : Arrays.stream(objects).map(Object::toString).collect(Collectors.toUnmodifiableList()); } } diff --git a/modules/platform/nuxeo-automation/nuxeo-automation-features/src/main/java/org/nuxeo/ecm/automation/core/operations/notification/SendMail.java b/modules/platform/nuxeo-automation/nuxeo-automation-features/src/main/java/org/nuxeo/ecm/automation/core/operations/notification/SendMail.java index 23f608aebd0..8d13bac054a 100644 --- a/modules/platform/nuxeo-automation/nuxeo-automation-features/src/main/java/org/nuxeo/ecm/automation/core/operations/notification/SendMail.java +++ b/modules/platform/nuxeo-automation/nuxeo-automation-features/src/main/java/org/nuxeo/ecm/automation/core/operations/notification/SendMail.java @@ -23,14 +23,16 @@ import static org.nuxeo.ecm.core.management.api.AdministrativeStatus.PASSIVE; import java.io.IOException; import java.io.InputStream; +import java.io.StringReader; +import java.io.StringWriter; import java.net.URL; import java.util.ArrayList; -import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; import javax.mail.MessagingException; -import javax.mail.Session; import javax.ws.rs.core.UriBuilder; import org.apache.commons.io.IOUtils; @@ -61,9 +63,13 @@ import org.nuxeo.ecm.core.api.model.impl.MapProperty; import org.nuxeo.ecm.core.api.model.impl.primitives.BlobProperty; import org.nuxeo.ecm.core.management.api.AdministrativeStatusManager; import org.nuxeo.ecm.platform.ec.notification.service.NotificationServiceHelper; -import org.nuxeo.ecm.platform.rendering.api.RenderingException; +import org.nuxeo.ecm.platform.rendering.fm.FreemarkerEngine; +import org.nuxeo.mail.MailException; +import org.nuxeo.mail.MailMessage; +import org.nuxeo.mail.MailService; import org.nuxeo.runtime.api.Framework; +import freemarker.template.Template; import freemarker.template.TemplateException; /** @@ -94,9 +100,6 @@ public class SendMail { @Param(name = "to", required = false) protected StringList to; - // Useful for tests. - protected Session mailSession; - /** * @since 5.9.1 */ @@ -140,8 +143,7 @@ public class SendMail { protected String viewId = "view_documents"; @OperationMethod(collector = DocumentModelCollector.class) - public DocumentModel run(DocumentModel doc) - throws TemplateException, RenderingException, OperationException, MessagingException, IOException { + public DocumentModel run(DocumentModel doc) throws TemplateException, OperationException, IOException { send(doc); return doc; } @@ -162,8 +164,7 @@ public class SendMail { } } - protected void send(DocumentModel doc) - throws TemplateException, RenderingException, OperationException, MessagingException, IOException { + protected void send(DocumentModel doc) throws TemplateException, OperationException, IOException { // TODO should sent one by one to each recipient? and have the template // rendered for each recipient? Use: "mailto" var name? try { @@ -179,28 +180,43 @@ public class SendMail { createDocUrlWithToken(MailTemplateHelper.getDocumentUrl(doc, viewId), (String) map.get("token"))); map.put("subject", subject); map.put("to", to); - map.put("toResolved", MailBox.fetchPersonsFromList(to, isStrict)); + List tos = MailBox.fetchPersonsFromList(to, isStrict); + map.put("toResolved", tos); map.put("from", from); - map.put("fromResolved", MailBox.fetchPersonsFromString(from, isStrict)); + List froms = MailBox.fetchPersonsFromString(from, isStrict); + map.put("fromResolved", froms); map.put("cc", cc); - map.put("ccResolved", MailBox.fetchPersonsFromList(cc, isStrict)); + List ccs = MailBox.fetchPersonsFromList(cc, isStrict); + map.put("ccResolved", ccs); map.put("bcc", bcc); - map.put("bccResolved", MailBox.fetchPersonsFromList(bcc, isStrict)); + List bccs = MailBox.fetchPersonsFromList(bcc, isStrict); + map.put("bccResolved", bccs); map.put("replyto", replyto); - map.put("replytoResolved", MailBox.fetchPersonsFromList(replyto, isStrict)); + List replytos = MailBox.fetchPersonsFromList(replyto, isStrict); + map.put("replytoResolved", replytos); map.put("viewId", viewId); map.put("baseUrl", NotificationServiceHelper.getNotificationService().getServerUrlPrefix()); map.put("Runtime", Framework.getRuntime()); - Mailer.Message msg = createMessage(doc, getContent(), map); - msg.setSubject(subject, "UTF-8"); - msg.setSentDate(new Date()); - - addMailBoxInfo(msg, map); - msg.send(); - } catch (NuxeoException | TemplateException | RenderingException | OperationException | MessagingException - | IOException e) { + var contentType = String.format("text/%s ; charset=utf-8", asHtml ? "html" : "plain"); + var msg = new MailMessage.Builder(asStringList(tos)).from(asStringList(froms)) + .cc(asStringList(ccs)) + .bcc(asStringList(bccs)) + .replyTo(asStringList(replytos)) + .attachments(getBlobs(doc)) + .subject(subject) + .content(renderContent(map), contentType) + .build(); + + // send + Framework.getService(MailService.class).sendMail(msg); + } catch (NuxeoException | TemplateException | OperationException | IOException e) { if (rollbackOnError) { + if (e instanceof MailException) { + // the Automation framework doesn't handle MailException (instance of NuxeoException) the same way + // as OperationException + throw new OperationException(e); + } throw e; } else { log.warn(String.format( @@ -223,7 +239,9 @@ public class SendMail { /** * @since 5.9.1 + * @deprecated since 2023.4 unused. {@link #send(DocumentModel)} now uses a {@link MailMessage.Builder} */ + @Deprecated(since = "2023.4") protected void addMailBoxInfo(Mailer.Message msg, Map map) throws MessagingException { addMailBoxInfoInMessageHeader(msg, AS.FROM, (List) map.get("fromResolved")); addMailBoxInfoInMessageHeader(msg, AS.TO, (List) map.get("toResolved")); @@ -244,43 +262,60 @@ public class SendMail { } } + /** + * @deprecated since 2023.4 unused. {@link #send(DocumentModel)} now uses a {@link MailMessage.Builder} + */ + @Deprecated(since = "2023.4") protected Mailer.Message createMessage(DocumentModel doc, String message, Map map) - throws MessagingException, TemplateException, RenderingException, IOException { + throws MessagingException, TemplateException, IOException { var composer = new Composer(); + return composer.newMixedMessage(message, map, asHtml ? "html" : "plain", getBlobs(doc)); + } + + protected String renderContent(Map map) throws IOException, OperationException, TemplateException { + var engine = new FreemarkerEngine(); + var reader = new StringReader(getContent()); + var template = new Template("@inline", reader, engine.getConfiguration(), "UTF-8"); + var writer = new StringWriter(); + var env = template.createProcessingEnvironment(map, writer, engine.getObjectWrapper()); + env.process(); + return writer.toString(); + } + + protected

List asStringList(List

inputList) { + return inputList.stream().map(Objects::toString).collect(Collectors.toList()); + } + + protected List getBlobs(DocumentModel doc) { if (blobXpath == null) { - if (asHtml) { - return composer.newHtmlMessage(message, map); - } else { - return composer.newTextMessage(message, map); - } - } else { - List blobs = new ArrayList<>(); - for (String xpath : blobXpath) { - try { - Property p = doc.getProperty(xpath); - if (p instanceof BlobProperty) { - getBlob(p.getValue(), blobs); - } else if (p instanceof ListProperty) { - for (Property pp : p) { - getBlob(pp.getValue(), blobs); - } - } else if (p instanceof MapProperty) { - for (Property sp : ((MapProperty) p).values()) { - getBlob(sp.getValue(), blobs); - } - } else { - Object o = p.getValue(); - if (o instanceof Blob) { - blobs.add((Blob) o); - } + return List.of(); + } + List blobs = new ArrayList<>(); + for (String xpath : blobXpath) { + try { + Property p = doc.getProperty(xpath); + if (p instanceof BlobProperty) { + getBlob(p.getValue(), blobs); + } else if (p instanceof ListProperty) { + for (Property pp : p) { + getBlob(pp.getValue(), blobs); + } + } else if (p instanceof MapProperty) { + for (Property sp : ((MapProperty) p).values()) { + getBlob(sp.getValue(), blobs); + } + } else { + Object o = p.getValue(); + if (o instanceof Blob) { + blobs.add((Blob) o); } - } catch (PropertyException pe) { - log.error("Error while fetching blobs: " + pe.getMessage()); - log.debug(pe, pe); } + } catch (PropertyException pe) { + log.error("Error while fetching blobs: " + pe.getMessage()); + log.debug(pe, pe); } - return composer.newMixedMessage(message, map, asHtml ? "html" : "plain", blobs); } + return blobs; } /** diff --git a/modules/platform/nuxeo-automation/nuxeo-automation-test/pom.xml b/modules/platform/nuxeo-automation/nuxeo-automation-test/pom.xml index 6b914d45fdb..8fd1d9eda03 100644 --- a/modules/platform/nuxeo-automation/nuxeo-automation-test/pom.xml +++ b/modules/platform/nuxeo-automation/nuxeo-automation-test/pom.xml @@ -189,6 +189,12 @@ org.mockito mockito-core + + org.nuxeo.ecm.platform + nuxeo-platform-notification + test-jar + test + diff --git a/modules/platform/nuxeo-automation/nuxeo-automation-test/src/test/java/org/nuxeo/ecm/automation/core/SendMailTest.java b/modules/platform/nuxeo-automation/nuxeo-automation-test/src/test/java/org/nuxeo/ecm/automation/core/SendMailTest.java index 99ff67ff874..0c50788a40f 100644 --- a/modules/platform/nuxeo-automation/nuxeo-automation-test/src/test/java/org/nuxeo/ecm/automation/core/SendMailTest.java +++ b/modules/platform/nuxeo-automation/nuxeo-automation-test/src/test/java/org/nuxeo/ecm/automation/core/SendMailTest.java @@ -40,6 +40,7 @@ import org.nuxeo.ecm.core.api.Blobs; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.test.CoreFeature; +import org.nuxeo.ecm.platform.ec.notification.NotificationFeature; import org.nuxeo.mail.SmtpMailServerFeature; import org.nuxeo.mail.SmtpMailServerFeature.MailMessage; import org.nuxeo.runtime.test.runner.Deploy; @@ -50,8 +51,7 @@ import org.nuxeo.runtime.test.runner.FeaturesRunner; * @author Bogdan Stefanescu */ @RunWith(FeaturesRunner.class) -@Features({ CoreFeature.class, SmtpMailServerFeature.class, AutomationFeature.class }) -@Deploy("org.nuxeo.ecm.platform.notification") +@Features({ CoreFeature.class, SmtpMailServerFeature.class, AutomationFeature.class, NotificationFeature.class }) @Deploy("org.nuxeo.ecm.platform.url") public class SendMailTest { diff --git a/modules/platform/nuxeo-automation/nuxeo-automation-test/src/test/java/org/nuxeo/ecm/automation/server/test/EmbeddedAutomationClientTest.java b/modules/platform/nuxeo-automation/nuxeo-automation-test/src/test/java/org/nuxeo/ecm/automation/server/test/EmbeddedAutomationClientTest.java index 4aa9d21843c..1520fd55ef6 100644 --- a/modules/platform/nuxeo-automation/nuxeo-automation-test/src/test/java/org/nuxeo/ecm/automation/server/test/EmbeddedAutomationClientTest.java +++ b/modules/platform/nuxeo-automation/nuxeo-automation-test/src/test/java/org/nuxeo/ecm/automation/server/test/EmbeddedAutomationClientTest.java @@ -50,7 +50,6 @@ import javax.inject.Inject; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; -import org.nuxeo.common.Environment; import org.nuxeo.common.utils.FileUtils; import org.nuxeo.ecm.automation.AutomationService; import org.nuxeo.ecm.automation.OperationException; @@ -398,18 +397,9 @@ public class EmbeddedAutomationClientTest extends AbstractAutomationClientTest { } @Test - public void testSendMail() throws Exception { - - // Set bad SMTP configuration - File file = new File(Environment.getDefault().getConfig(), "mail.properties"); - file.getParentFile().mkdirs(); - List mailProperties = new ArrayList<>(); - mailProperties.add(String.format("mail.smtp.host = %s", "badHostName")); - mailProperties.add(String.format("mail.smtp.port = %s", "2525")); - mailProperties.add(String.format("mail.smtp.connectiontimeout = %s", "1000")); - mailProperties.add(String.format("mail.smtp.timeout = %s", "1000")); - org.apache.commons.io.FileUtils.writeLines(file, mailProperties); - + @Deploy("org.nuxeo.mail") + @Deploy("org.nuxeo.ecm.automation.test.test:bad-mail-sender-contrib.xml") + public void testSendMailToBadHost() throws Exception { HttpAutomationRequest operationRequest = session.newRequest(SendMail.ID) .setInput("doc:/") .set("from", "sender@nuxeo.com") diff --git a/modules/platform/nuxeo-automation/nuxeo-automation-test/src/test/resources/bad-mail-sender-contrib.xml b/modules/platform/nuxeo-automation/nuxeo-automation-test/src/test/resources/bad-mail-sender-contrib.xml new file mode 100644 index 00000000000..47311cd3f0b --- /dev/null +++ b/modules/platform/nuxeo-automation/nuxeo-automation-test/src/test/resources/bad-mail-sender-contrib.xml @@ -0,0 +1,12 @@ + + + org.nuxeo.mail.MailServiceComponent + + + badHostName + 2525 + 1000 + 1000 + + + diff --git a/modules/platform/nuxeo-easyshare-core/src/main/java/org/nuxeo/easyshare/EasyShare.java b/modules/platform/nuxeo-easyshare-core/src/main/java/org/nuxeo/easyshare/EasyShare.java index c61ac001c0b..e180456523a 100755 --- a/modules/platform/nuxeo-easyshare-core/src/main/java/org/nuxeo/easyshare/EasyShare.java +++ b/modules/platform/nuxeo-easyshare-core/src/main/java/org/nuxeo/easyshare/EasyShare.java @@ -23,6 +23,7 @@ import java.util.Date; import java.util.HashMap; import java.util.Map; +import javax.mail.MessagingException; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.Path; @@ -50,6 +51,7 @@ import org.nuxeo.ecm.platform.ec.notification.service.NotificationServiceHelper; import org.nuxeo.ecm.platform.notification.api.Notification; import org.nuxeo.ecm.webengine.model.WebObject; import org.nuxeo.ecm.webengine.model.impl.ModuleRoot; +import org.nuxeo.mail.MailException; import org.nuxeo.runtime.api.Framework; /** @@ -278,7 +280,6 @@ public class EasyShare extends ModuleRoot { log.debug("Easyshare: starting email"); EmailHelper emailHelper = new EmailHelper(); Map mailProps = new HashMap<>(); - mailProps.put("mail.from", Framework.getProperty("mail.from", "system@nuxeo.com")); mailProps.put("mail.to", email); mailProps.put("ip", getIpAddr()); mailProps.put("docShare", docShare); @@ -298,8 +299,9 @@ public class EasyShare extends ModuleRoot { mailProps.putAll(mail); - emailHelper.sendmail(mailProps); - + emailHelper.sendMailMessage(mailProps); + } catch (MailException me) { + throw me; } catch (NuxeoException e) { log.warn(e.getMessage()); } diff --git a/modules/platform/nuxeo-invite/src/main/java/org/nuxeo/ecm/user/invite/UserInvitationComponent.java b/modules/platform/nuxeo-invite/src/main/java/org/nuxeo/ecm/user/invite/UserInvitationComponent.java index 186822e70ed..e995d32ea03 100644 --- a/modules/platform/nuxeo-invite/src/main/java/org/nuxeo/ecm/user/invite/UserInvitationComponent.java +++ b/modules/platform/nuxeo-invite/src/main/java/org/nuxeo/ecm/user/invite/UserInvitationComponent.java @@ -29,18 +29,12 @@ import java.io.Serializable; import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; -import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -import javax.mail.Message; import javax.mail.MessagingException; -import javax.mail.Session; -import javax.mail.Transport; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeMessage; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; @@ -70,7 +64,8 @@ import org.nuxeo.ecm.platform.usermanager.NuxeoPrincipalImpl; import org.nuxeo.ecm.platform.usermanager.UserConfig; import org.nuxeo.ecm.platform.usermanager.UserManager; import org.nuxeo.ecm.platform.usermanager.exceptions.UserAlreadyExistsException; -import org.nuxeo.mail.MailSessionBuilder; +import org.nuxeo.mail.MailMessage; +import org.nuxeo.mail.MailService; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.model.ComponentInstance; import org.nuxeo.runtime.model.DefaultComponent; @@ -536,20 +531,12 @@ public class UserInvitationComponent extends DefaultComponent implements UserInv protected void generateMail(String destination, String copy, String title, String content) throws MessagingException { - Session session = MailSessionBuilder.fromNuxeoConf().build(); + var message = new MailMessage.Builder(destination).cc(isBlank(copy) ? List.of() : List.of(copy)) + .subject(title) + .content(content, "text/html; charset=utf-8") + .build(); - MimeMessage msg = new MimeMessage(session); - msg.setFrom(new InternetAddress(session.getProperty("mail.from"))); - msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(destination, false)); - if (!isBlank(copy)) { - msg.addRecipient(Message.RecipientType.CC, new InternetAddress(copy, false)); - } - - msg.setSubject(title, "UTF-8"); - msg.setSentDate(new Date()); - msg.setContent(content, "text/html; charset=utf-8"); - - Transport.send(msg); + Framework.getService(MailService.class).sendMail(message); } @Override diff --git a/modules/platform/nuxeo-mail/pom.xml b/modules/platform/nuxeo-mail/pom.xml index ecc77a8ca5a..8a990950afd 100644 --- a/modules/platform/nuxeo-mail/pom.xml +++ b/modules/platform/nuxeo-mail/pom.xml @@ -30,7 +30,10 @@ javax.mail mail - + + com.sun.activation + javax.activation + diff --git a/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/BlobDataSource.java b/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/BlobDataSource.java new file mode 100644 index 00000000000..ff9b23b2027 --- /dev/null +++ b/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/BlobDataSource.java @@ -0,0 +1,59 @@ +/* + * (C) Copyright 2023 Nuxeo (http://nuxeo.com/) and others. + * + * 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.nuxeo.mail; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import javax.activation.DataSource; + +import org.nuxeo.ecm.core.api.Blob; + +/** + * Wraps a {@link Blob} into a {@link DataSource}. + * + * @since 2023.4 + */ +public class BlobDataSource implements DataSource { + + protected Blob blob; + + public BlobDataSource(Blob blob) { + this.blob = blob; + } + + @Override + public String getContentType() { + return blob.getMimeType(); + } + + @Override + public InputStream getInputStream() throws IOException { + return blob.getStream(); + } + + @Override + public String getName() { + return blob.getFilename(); + } + + @Override + public OutputStream getOutputStream() { + throw new UnsupportedOperationException("BlobDataSource is read-only"); + } +} diff --git a/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/JndiSMTPMailSender.java b/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/JndiSMTPMailSender.java new file mode 100644 index 00000000000..30730981aef --- /dev/null +++ b/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/JndiSMTPMailSender.java @@ -0,0 +1,38 @@ +/* + * (C) Copyright 2023 Nuxeo (http://nuxeo.com/) and others. + * + * 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.nuxeo.mail; + +/** + * @since 2023.4 + * @deprecated since 2023.4 Compatibility implementation of MailSender relying on a MailSession available through JNDI. + *

+ * Use a {@link MailSenderDescriptor} to configure your {@link MailSender} properly. + */ +@Deprecated(since = "2023.4") +public class JndiSMTPMailSender extends SMTPMailSender { + + public static final String JNDI_SESSION_NAME = "jndiSessionName"; + + public JndiSMTPMailSender(String jndiName) { + super(MailSessionBuilder.fromJndi(jndiName).build()); + } + + public JndiSMTPMailSender(MailSenderDescriptor descriptor) { + super(MailSessionBuilder.fromJndi(descriptor.properties.get(JNDI_SESSION_NAME)).build()); + } + +} diff --git a/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailException.java b/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailException.java new file mode 100644 index 00000000000..556630e9945 --- /dev/null +++ b/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailException.java @@ -0,0 +1,42 @@ +/* + * (C) Copyright 2023 Nuxeo (http://nuxeo.com/) and others. + * + * 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.nuxeo.mail; + +import org.nuxeo.ecm.core.api.NuxeoException; + +/** + * Identifies NuxeoExceptions related to {@link MailMessage} sending. + * + * @since 2023.4 + */ +public class MailException extends NuxeoException { + + private static final long serialVersionUID = 1L; + + public MailException(String message) { + super(message); + } + + public MailException(String message, Throwable cause) { + super(message, cause); + } + + public MailException(Throwable cause) { + super(cause); + } + +} diff --git a/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailMessage.java b/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailMessage.java new file mode 100644 index 00000000000..bee4481fa30 --- /dev/null +++ b/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailMessage.java @@ -0,0 +1,286 @@ +/* + * (C) Copyright 2023 Nuxeo (http://nuxeo.com/) and others. + * + * 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.nuxeo.mail; + +import static org.nuxeo.mail.MailServiceImpl.DEFAULT_SENDER; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import org.nuxeo.ecm.core.api.Blob; + +/** + * A class representing a mail message. To be used to construct the final mail depending on the underlying mail service + * implementation. + * + * @since 2023.4 + */ +public final class MailMessage { + + private final List tos; + + private final List froms; + + private final List ccs; + + private final List bccs; + + private final List replyTos; + + private final List attachments; + + private final Date date; + + private final String subject; + + private final Charset subjectCharset; + + private final Object content; + + private final String contentType; + + private final String senderName; + + private MailMessage(Builder builder) { + this.tos = List.copyOf(builder.tos); + this.froms = List.copyOf(builder.froms); + this.ccs = List.copyOf(builder.ccs); + this.bccs = List.copyOf(builder.bccs); + this.replyTos = List.copyOf(builder.replyTos); + this.attachments = List.copyOf(builder.attachments); + this.date = builder.date; + this.subject = builder.subject; + this.subjectCharset = builder.subjectCharset; + this.content = builder.content; + this.contentType = builder.contentType; + this.senderName = builder.senderName; + } + + public List getTos() { + return tos; + } + + public List getFroms() { + return froms; + } + + public List getCcs() { + return ccs; + } + + public List getBccs() { + return bccs; + } + + public List getReplyTos() { + return replyTos; + } + + public List getAttachments() { + return attachments; + } + + public boolean hasAttachments() { + return !attachments.isEmpty(); + } + + public Date getDate() { + return date; + } + + public String getSubject() { + return subject; + } + + public Charset getSubjectCharset() { + return subjectCharset; + } + + public Object getContent() { + return content; + } + + public String getContentType() { + return contentType; + } + + public String getSenderName() { + return senderName; + } + + public static class Builder { + + protected final List tos = new ArrayList<>(); + + protected final List froms = new ArrayList<>(); + + protected final List ccs = new ArrayList<>(); + + protected final List bccs = new ArrayList<>(); + + protected final List replyTos = new ArrayList<>(); + + protected final List attachments = new ArrayList<>(); + + protected Date date = new Date(); + + protected String subject; + + protected Charset subjectCharset = StandardCharsets.UTF_8; + + protected Object content; + + protected String contentType = "text/plain; charset=utf-8"; + + protected String senderName = DEFAULT_SENDER; + + public Builder(List tos) { + this.tos.addAll(tos); + } + + public Builder(String to, String... tos) { + this.tos.add(to); + Collections.addAll(this.tos, tos); + } + + public Builder(MailMessage m) { + this(m.tos); + from(m.froms).cc(m.ccs) + .bcc(m.bccs) + .replyTo(m.replyTos) + .attachments(m.attachments) + .date(m.date) + .subject(m.subject, m.subjectCharset) + .content(m.content, m.contentType) + .senderName(m.senderName); + } + + public Builder to(List to) { + this.tos.addAll(to); + return this; + } + + public Builder to(String to, String... tos) { + this.tos.add(to); + Collections.addAll(this.tos, tos); + return this; + } + + public Builder from(List from) { + this.froms.addAll(from); + return this; + } + + public Builder from(String from, String... froms) { + this.froms.add(from); + Collections.addAll(this.froms, froms); + return this; + } + + public Builder cc(List cc) { + this.ccs.addAll(cc); + return this; + } + + public Builder cc(String cc, String... ccs) { + this.ccs.add(cc); + Collections.addAll(this.ccs, ccs); + return this; + } + + public Builder bcc(List bcc) { + this.bccs.addAll(bcc); + return this; + } + + public Builder bcc(String bcc, String... bccs) { + this.bccs.add(bcc); + Collections.addAll(this.bccs, bccs); + return this; + } + + public Builder replyTo(List replyTo) { + this.replyTos.addAll(replyTo); + return this; + } + + public Builder replyTo(String replyTo, String... replyTos) { + this.replyTos.add(replyTo); + Collections.addAll(this.replyTos, replyTos); + return this; + } + + public Builder attachments(List attachments) { + this.attachments.addAll(attachments); + return this; + } + + public Builder attachments(Blob attachment, Blob... attachments) { + this.attachments.add(attachment); + Collections.addAll(this.attachments, attachments); + return this; + } + + public Builder date(Date date) { + this.date = date; + return this; + } + + public Builder subject(String subject) { + this.subject = subject; + return this; + } + + public Builder subject(String subject, Charset charset) { + this.subject = subject; + this.subjectCharset = charset; + return this; + } + + public Builder subject(String subject, String charset) { + this.subject = subject; + this.subjectCharset = Charset.forName(charset); + return this; + } + + public Builder content(Object content) { + this.content = content; + return this; + } + + public Builder content(Object content, String contentType) { + this.content = content; + this.contentType = contentType; + return this; + } + + public Builder senderName(String senderName) { + this.senderName = senderName; + return this; + } + + public MailMessage build() { + return new MailMessage(this); + } + + } + +} diff --git a/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailSender.java b/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailSender.java new file mode 100644 index 00000000000..842a4015071 --- /dev/null +++ b/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailSender.java @@ -0,0 +1,28 @@ +/* + * (C) Copyright 2023 Nuxeo (http://nuxeo.com/) and others. + * + * 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.nuxeo.mail; + +/** + * Interface for sending {@link MailMessage}s. + * + * @since 2023.4 + */ +public interface MailSender { + + void sendMail(MailMessage message); + +} diff --git a/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailSenderDescriptor.java b/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailSenderDescriptor.java new file mode 100644 index 00000000000..dc6e2f813b5 --- /dev/null +++ b/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailSenderDescriptor.java @@ -0,0 +1,61 @@ +/* + * (C) Copyright 2023 Nuxeo (http://nuxeo.com/) and others. + * + * 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.nuxeo.mail; + +import java.util.HashMap; +import java.util.Map; + +import org.nuxeo.common.xmap.annotation.XNode; +import org.nuxeo.common.xmap.annotation.XNodeMap; +import org.nuxeo.common.xmap.annotation.XObject; +import org.nuxeo.runtime.model.Descriptor; + +/** + * Describes a {@link MailSender} which can be parameterized with properties. + * + * @since 2023.4 + */ +@XObject("sender") +public class MailSenderDescriptor implements Descriptor { + + @XNode("@name") + protected String name; + + @XNode("@class") + protected Class klass; + + @XNodeMap(value = "property", key = "@name", type = HashMap.class, componentType = String.class) + protected Map properties; + + @Override + public String getId() { + return name; + } + + public MailSender newInstance() { + if (!MailSender.class.isAssignableFrom(klass)) { + throw new IllegalArgumentException( + "Cannot instantiate class: " + klass + ", class must implement MailSender"); + } + try { + return klass.getDeclaredConstructor(MailSenderDescriptor.class).newInstance(this); + } catch (ReflectiveOperationException e) { + throw new IllegalArgumentException("Cannot instantiate: " + klass, e); + } + } + +} diff --git a/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailService.java b/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailService.java new file mode 100644 index 00000000000..97853f117e4 --- /dev/null +++ b/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailService.java @@ -0,0 +1,29 @@ +/* + * (C) Copyright 2023 Nuxeo (http://nuxeo.com/) and others. + * + * 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.nuxeo.mail; + +/** + * Service used to send mails. + * + * @see MailMessage + * @since 2023.4 + */ +public interface MailService { + + void sendMail(MailMessage msg); + +} diff --git a/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailServiceImpl.java b/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailServiceImpl.java new file mode 100644 index 00000000000..1f2130a91e1 --- /dev/null +++ b/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailServiceImpl.java @@ -0,0 +1,88 @@ +/* + * (C) Copyright 2023 Nuxeo (http://nuxeo.com/) and others. + * + * 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.nuxeo.mail; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.nuxeo.runtime.RuntimeMessage; +import org.nuxeo.runtime.model.ComponentContext; +import org.nuxeo.runtime.model.DefaultComponent; + +import java.util.HashMap; +import java.util.Map; + +/** + * {@link MailService} implementation, leveraging {@link MailSender} to effectively send the {@link MailMessage}. + * + * @since 2023.4 + */ +public class MailServiceImpl extends DefaultComponent implements MailService { + + private static final Logger log = LogManager.getLogger(MailServiceImpl.class); + + public static final String DEFAULT_SENDER = "default"; + + public static final String SENDERS_XP = "senders"; + + protected final Map senders = new HashMap<>(); + + @Override + public void start(ComponentContext context) { + super.start(context); + this. getDescriptors(SENDERS_XP).forEach(d -> { + if (d.klass.equals(JndiSMTPMailSender.class)) { + var msg = String.format( + "The sender: %s contribution uses JndiSMTPMailSender which is deprecated. Please contribute a SMTPMailSender instead", + d.name); + log.warn(msg); + addRuntimeMessage(RuntimeMessage.Level.WARNING, msg, RuntimeMessage.Source.EXTENSION, d.name); + } + senders.put(d.getId(), d.newInstance()); + }); + } + + @Override + public void stop(ComponentContext context) throws InterruptedException { + super.stop(context); + senders.clear(); + } + + @Override + public void sendMail(MailMessage msg) { + String senderName = msg.getSenderName(); + if (senders.containsKey(senderName)) { + senders.get(senderName).sendMail(msg); + } else { + throw new MailException("Couldn't send mail. MailSender: " + senderName + " not found"); + } + } + + /** + * @return the sender name to use in {@link MailMessage} + * @deprecated since 2023.4 This is a fallback method to register a {@link JndiSMTPMailSender} on the fly when only + * given a custom JNDI session name. Use a {@link MailSenderDescriptor} to define a custom + * {@link MailSender} instead + */ + @Deprecated(since = "2023.4") + public String registerJndiSMTPSender(String jndiName) { + log.warn( + "Registering a JndiSMTPMailSender which is deprecated. Please contribute a SMTPMailSender instead with the properties of the following JNDI session: {}", + jndiName); + senders.computeIfAbsent(jndiName, JndiSMTPMailSender::new); + return jndiName; + } +} diff --git a/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailSessionBuilder.java b/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailSessionBuilder.java index f47d676438d..0e1bbbd95ba 100644 --- a/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailSessionBuilder.java +++ b/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/MailSessionBuilder.java @@ -89,6 +89,15 @@ public class MailSessionBuilder { return new FromProperties(properties); } + /** + * Creates a {@link FromProperties builder} instantiating a new session. + */ + public static FromProperties fromProperties(Map properties) { + var props = new Properties(); + props.putAll(properties); + return new FromProperties(props); + } + public static class FromJndi extends AbstractFrom { protected String jndiSessionName; diff --git a/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/SMTPMailSender.java b/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/SMTPMailSender.java new file mode 100644 index 00000000000..9b45550fc8f --- /dev/null +++ b/modules/platform/nuxeo-mail/src/main/java/org/nuxeo/mail/SMTPMailSender.java @@ -0,0 +1,124 @@ +/* + * (C) Copyright 2023 Nuxeo (http://nuxeo.com/) and others. + * + * 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.nuxeo.mail; + +import static org.nuxeo.mail.MailConstants.CONFIGURATION_MAIL_FROM; + +import java.util.List; + +import javax.activation.DataHandler; +import javax.mail.Address; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.AddressException; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; + +import org.nuxeo.ecm.core.api.Blob; + +/** + * Default implementation of {@link MailSender} building {@link MimeMessage}s and sending via SMTP protocol. + * + * @since 2023.4 + */ +public class SMTPMailSender implements MailSender { + + protected final Session session; + + public SMTPMailSender(MailSenderDescriptor descriptor) { + this(MailSessionBuilder.fromProperties(descriptor.properties).build()); + } + + protected SMTPMailSender(Session session) { + this.session = session; + } + + @Override + public void sendMail(MailMessage message) { + try { + var mimeMessage = buildMimeMessage(message); + Transport.send(mimeMessage); + } catch (MessagingException e) { + throw new MailException("An error occurred while sending a mail", e); + } + } + + protected MimeMessage composeMimeMessage(MailMessage msg) throws MessagingException { + var mail = new MimeMessage(session); + Object content = msg.getContent(); + String contentType = msg.getContentType(); + if (msg.hasAttachments()) { + MimeBodyPart body = null; + if (content != null) { + body = new MimeBodyPart(); + body.setContent(msg.getContent(), contentType); + } + MimeMultipart bodyParts = assembleMultiPart(body, msg.getAttachments()); + mail.setContent(bodyParts); + } else if (content != null) { + mail.setContent(msg.getContent(), contentType); + } + + return mail; + } + + protected MimeMultipart assembleMultiPart(MimeBodyPart body, List attachments) throws MessagingException { + var mp = new MimeMultipart(); + if (body != null) { + mp.addBodyPart(body); + } + for (Blob blob : attachments) { + MimeBodyPart a = new MimeBodyPart(); + a.setDataHandler(new DataHandler(new BlobDataSource(blob))); + a.setFileName(blob.getFilename()); + mp.addBodyPart(a); + } + return mp; + } + + protected Address[] toAddresses(List list) { + return list.stream().map(a -> { + try { + return new InternetAddress(a); + } catch (AddressException e) { + throw new MailException("Could not parse mail address: " + a, e); + } + }).toArray(InternetAddress[]::new); + } + + protected MimeMessage buildMimeMessage(MailMessage message) throws MessagingException { + var effectiveMessage = message.getFroms().isEmpty() ? addFroms(message) : message; + MimeMessage mimeMsg = composeMimeMessage(effectiveMessage); + + mimeMsg.addFrom(toAddresses(effectiveMessage.getFroms())); + mimeMsg.setRecipients(MimeMessage.RecipientType.TO, toAddresses(effectiveMessage.getTos())); + mimeMsg.setRecipients(MimeMessage.RecipientType.CC, toAddresses(effectiveMessage.getCcs())); + mimeMsg.setRecipients(MimeMessage.RecipientType.BCC, toAddresses(effectiveMessage.getBccs())); + mimeMsg.setReplyTo(toAddresses(effectiveMessage.getReplyTos())); + mimeMsg.setSentDate(effectiveMessage.getDate()); + mimeMsg.setSubject(effectiveMessage.getSubject(), effectiveMessage.getSubjectCharset().toString()); + return mimeMsg; + } + + protected MailMessage addFroms(MailMessage message) { + return new MailMessage.Builder(message).from(session.getProperty(CONFIGURATION_MAIL_FROM)).build(); + } + +} diff --git a/modules/platform/nuxeo-mail/src/main/resources/META-INF/MANIFEST.MF b/modules/platform/nuxeo-mail/src/main/resources/META-INF/MANIFEST.MF index 8cf85105e27..676805c546b 100755 --- a/modules/platform/nuxeo-mail/src/main/resources/META-INF/MANIFEST.MF +++ b/modules/platform/nuxeo-mail/src/main/resources/META-INF/MANIFEST.MF @@ -3,3 +3,4 @@ Bundle-ManifestVersion: 2 Bundle-Name: Nuxeo Mail Services Bundle-SymbolicName: org.nuxeo.mail;singleton:=true Bundle-Vendor: Nuxeo +Nuxeo-Component: OSGI-INF/mail-service.xml diff --git a/modules/platform/nuxeo-mail/src/main/resources/OSGI-INF/mail-service.xml b/modules/platform/nuxeo-mail/src/main/resources/OSGI-INF/mail-service.xml new file mode 100644 index 00000000000..f2fb75d2a27 --- /dev/null +++ b/modules/platform/nuxeo-mail/src/main/resources/OSGI-INF/mail-service.xml @@ -0,0 +1,31 @@ + + + + A service to send mails. + + + + + + + + + + + Extension point to register mail senders. +

+ Senders can be configured with properties. + For example: + + + foo@bar.baz + ping@p.org + + + + The class attribute needs to reference a class implementing org.nuxeo.mail.MailSender. + + + + + diff --git a/modules/platform/nuxeo-mail/src/test/java/org/nuxeo/mail/SmtpMailServerFeature.java b/modules/platform/nuxeo-mail/src/test/java/org/nuxeo/mail/SmtpMailServerFeature.java index f6263301a36..b1829c1980b 100644 --- a/modules/platform/nuxeo-mail/src/test/java/org/nuxeo/mail/SmtpMailServerFeature.java +++ b/modules/platform/nuxeo-mail/src/test/java/org/nuxeo/mail/SmtpMailServerFeature.java @@ -22,36 +22,18 @@ package org.nuxeo.mail; import static java.util.Objects.isNull; import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNullElseGet; -import static java.util.stream.Collectors.toMap; import static org.junit.Assert.assertEquals; -import static org.nuxeo.mail.MailConstants.CONFIGURATION_MAIL_FROM; -import static org.nuxeo.mail.MailConstants.CONFIGURATION_MAIL_PREFIX; -import static org.nuxeo.mail.MailConstants.CONFIGURATION_MAIL_SMTP_FROM; -import static org.nuxeo.mail.MailConstants.CONFIGURATION_MAIL_SMTP_HOST; import static org.nuxeo.mail.MailConstants.CONFIGURATION_MAIL_SMTP_PORT; -import static org.nuxeo.mail.MailConstants.CONFIGURATION_MAIL_TRANSPORT_PROTOCOL; -import static org.nuxeo.mail.MailConstants.DEFAULT_MAIL_JNDI_NAME; -import static org.nuxeo.mail.MailConstants.NUXEO_CONFIGURATION_MAIL_TRANSPORT_HOST; -import static org.nuxeo.mail.MailConstants.NUXEO_CONFIGURATION_MAIL_TRANSPORT_PORT; -import static org.nuxeo.mail.MailConstants.NUXEO_CONFIGURATION_MAIL_TRANSPORT_PROTOCOL; import java.io.IOException; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.mail.Session; -import javax.naming.Context; -import javax.naming.NameNotFoundException; -import javax.naming.NamingException; - import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; @@ -60,12 +42,12 @@ import org.apache.logging.log4j.Logger; import org.junit.runners.model.FrameworkMethod; import org.nuxeo.ecm.core.api.NuxeoException; import org.nuxeo.runtime.api.Framework; -import org.nuxeo.runtime.jtajca.NuxeoContainer; import org.nuxeo.runtime.test.runner.Deploy; import org.nuxeo.runtime.test.runner.Features; import org.nuxeo.runtime.test.runner.FeaturesRunner; import org.nuxeo.runtime.test.runner.RunnerFeature; import org.nuxeo.runtime.test.runner.RuntimeFeature; +import org.nuxeo.runtime.test.runner.RuntimeHarness; import com.dumbster.smtp.SimpleSmtpServer; import com.dumbster.smtp.SmtpMessage; @@ -74,16 +56,14 @@ import com.google.inject.Binder; /** * @since 11.1 */ -@Deploy("org.nuxeo.runtime.jtajca") @Features(RuntimeFeature.class) +@Deploy("org.nuxeo.runtime.jtajca") +@Deploy("org.nuxeo.mail") +@Deploy("org.nuxeo.mail.test") public class SmtpMailServerFeature implements RunnerFeature { private static final Logger log = LogManager.getLogger(SmtpMailServerFeature.class); - protected static final String SERVER_HOST = "127.0.0.1"; - - protected static final String DEFAULT_MAIL_SENDER = "noreply@nuxeo.com"; - /** * Pattern to parse and retrieve the text date in {@link DateTimeFormatter#RFC_1123_DATE_TIME} format. *

@@ -97,28 +77,13 @@ public class SmtpMailServerFeature implements RunnerFeature { protected MailsResult result = new MailsResult(); - protected Map backupProperties; - @Override public void configure(FeaturesRunner runner, Binder binder) { binder.bind(MailsResult.class).toInstance(result); } @Override - public void beforeMethodRun(FeaturesRunner runner, FrameworkMethod method, Object test) { - start(); - } - - @Override - public void afterMethodRun(FeaturesRunner runner, FrameworkMethod method, Object test) { - stop(); - } - - /** - * Starts a dummy SMTP server {@link SimpleSmtpServer}. - */ - protected void start() { - // Create the server and start it + public void start(FeaturesRunner runner) throws Exception { try { server = SimpleSmtpServer.start(SimpleSmtpServer.AUTO_SMTP_PORT); result.skip = 0; @@ -128,81 +93,23 @@ public class SmtpMailServerFeature implements RunnerFeature { int serverPort = server.getPort(); log.debug("Fake smtp server started on port: {}", serverPort); - // backup previous Framework properties - Properties frameworkProperties = Framework.getProperties(); - backupProperties = frameworkProperties.stringPropertyNames() - .stream() - .filter(k -> k.startsWith(CONFIGURATION_MAIL_PREFIX)) - .collect(toMap(Function.identity(), frameworkProperties::get)); - - // build Properties for javax.mail.Session retrieval - Properties properties = new Properties(); - properties.putAll(backupProperties); - properties.put(CONFIGURATION_MAIL_TRANSPORT_PROTOCOL, "smtp"); - properties.put(CONFIGURATION_MAIL_SMTP_HOST, SERVER_HOST); - properties.put(CONFIGURATION_MAIL_SMTP_PORT, String.valueOf(serverPort)); - properties.putIfAbsent(CONFIGURATION_MAIL_SMTP_FROM, DEFAULT_MAIL_SENDER); - properties.putIfAbsent(CONFIGURATION_MAIL_FROM, DEFAULT_MAIL_SENDER); - - binding(properties); - - // apply configuration to Framework - frameworkProperties.put(NUXEO_CONFIGURATION_MAIL_TRANSPORT_PROTOCOL, "smtp"); - frameworkProperties.put(NUXEO_CONFIGURATION_MAIL_TRANSPORT_HOST, SERVER_HOST); - frameworkProperties.put(NUXEO_CONFIGURATION_MAIL_TRANSPORT_PORT, String.valueOf(serverPort)); + Framework.getProperties().setProperty("nuxeo.test." + CONFIGURATION_MAIL_SMTP_PORT, String.valueOf(serverPort)); + RuntimeHarness harness = runner.getFeature(RuntimeFeature.class).getHarness(); + harness.deployContrib("org.nuxeo.mail.test", "OSGI-INF/test-smtp-mail-sender-contrib.xml"); } - /** - * Stops the dummy server. - */ - protected void stop() { - if (server != null) { - server.stop(); - } - unbind(); - clear(); - } - - /** - * Binds the {@link MailConstants#DEFAULT_MAIL_JNDI_NAME} resource to a mail {@link Session}. - */ - protected void binding(Properties properties) { - try { - Context context = NuxeoContainer.getRootContext(); - context.bind(DEFAULT_MAIL_JNDI_NAME, MailSessionBuilder.fromProperties(properties).build()); - } catch (NamingException ne) { - throw new NuxeoException("Unable to bind the SMTP server in jndi", ne); - } + @Override + public void beforeSetup(FeaturesRunner runner, FrameworkMethod method, Object test) { + result.clearMails(); } - /** - * Unbinds the {@link MailConstants#DEFAULT_MAIL_JNDI_NAME} resource. - */ - protected void unbind() { - try { - Context context = NuxeoContainer.getRootContext(); - context.unbind(DEFAULT_MAIL_JNDI_NAME); - } catch (NameNotFoundException nnf) { - log.trace("{} is not found", DEFAULT_MAIL_JNDI_NAME, nnf); - } catch (NamingException ne) { - throw new NuxeoException("Unable to unbind the SMTP server in jndi", ne); + @Override + public void stop(FeaturesRunner runner) throws Exception { + if (server != null) { + server.stop(); } } - /** - * Removes the added properties during mail processing. - * - * @since 11.1 - */ - protected void clear() { - var frameworkProperties = Framework.getProperties(); - frameworkProperties.remove(NUXEO_CONFIGURATION_MAIL_TRANSPORT_PROTOCOL); - frameworkProperties.remove(NUXEO_CONFIGURATION_MAIL_TRANSPORT_HOST); - frameworkProperties.remove(NUXEO_CONFIGURATION_MAIL_TRANSPORT_PORT); - // restore backup properties - frameworkProperties.putAll(backupProperties); - } - /** * Wrapper of the mails messages {@link MailMessage}. */ diff --git a/modules/platform/nuxeo-mail/src/test/java/org/nuxeo/mail/TestSMTPMailSender.java b/modules/platform/nuxeo-mail/src/test/java/org/nuxeo/mail/TestSMTPMailSender.java new file mode 100644 index 00000000000..63bed75b9ff --- /dev/null +++ b/modules/platform/nuxeo-mail/src/test/java/org/nuxeo/mail/TestSMTPMailSender.java @@ -0,0 +1,59 @@ +/* + * (C) Copyright 2013-2023 Nuxeo (http://nuxeo.com/) and others. + * + * 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.nuxeo.mail; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import javax.inject.Inject; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.nuxeo.ecm.core.api.impl.blob.StringBlob; +import org.nuxeo.runtime.test.runner.Features; +import org.nuxeo.runtime.test.runner.FeaturesRunner; + +/** + * @since 2023.4 + */ +@RunWith(FeaturesRunner.class) +@Features(SmtpMailServerFeature.class) +public class TestSMTPMailSender { + + @Inject + protected MailService mailService; + + @Inject + protected SmtpMailServerFeature.MailsResult result; + + @Test + public void testSendEmptyContentWithAttachment() { + var foo = new StringBlob("bar"); + foo.setFilename("foo"); + var mail = new MailMessage.Builder("someone@nuxeo.com").attachments(foo).build(); + + mailService.sendMail(mail); + + assertEquals(1, result.getSize()); + var received = result.getMails().get(0); + assertEquals("noreply@nuxeo.com", received.getSenders().get(0)); + assertEquals("someone@nuxeo.com", received.getRecipients().get(0)); + assertTrue(received.content.contains( + "Content-Type: text/plain; charset=us-ascii; name=fooContent-Transfer-Encoding: 7bitContent-Disposition: attachment; filename=foo")); + } + +} diff --git a/modules/platform/nuxeo-mail/src/test/java/org/nuxeo/mail/TestSmtpMailServerFeature.java b/modules/platform/nuxeo-mail/src/test/java/org/nuxeo/mail/TestSmtpMailServerFeature.java index 80561adb967..a33a59bcc31 100644 --- a/modules/platform/nuxeo-mail/src/test/java/org/nuxeo/mail/TestSmtpMailServerFeature.java +++ b/modules/platform/nuxeo-mail/src/test/java/org/nuxeo/mail/TestSmtpMailServerFeature.java @@ -19,13 +19,9 @@ package org.nuxeo.mail; -import static javax.mail.Message.RecipientType.TO; import static org.junit.Assert.assertEquals; import javax.inject.Inject; -import javax.mail.MessagingException; -import javax.mail.Transport; -import javax.mail.internet.MimeMessage; import org.junit.Test; import org.junit.runner.RunWith; @@ -39,23 +35,23 @@ import org.nuxeo.runtime.test.runner.FeaturesRunner; @Features(SmtpMailServerFeature.class) public class TestSmtpMailServerFeature { + @Inject + protected MailService mailService; + @Inject protected SmtpMailServerFeature.MailsResult result; @Test - public void testFeature() throws MessagingException { - var session = MailSessionBuilder.fromNuxeoConf().build(); - var message = new MimeMessage(session); - message.addRecipients(TO, "someone@nuxeo.com"); - message.setText("Some content"); - Transport.send(message); - - // assert email was sent + public void testFeature() { + var mail = new MailMessage.Builder("someone@nuxeo.com").content("Some content").build(); + + mailService.sendMail(mail); + assertEquals(1, result.getSize()); } @Test - public void testFeatureResultIsolation() throws MessagingException { + public void testFeatureResultIsolation() { // send a mail again and check we only have this one testFeature(); } diff --git a/modules/platform/nuxeo-mail/src/test/resources/META-INF/MANIFEST.MF b/modules/platform/nuxeo-mail/src/test/resources/META-INF/MANIFEST.MF index 2a37a7e7a21..72d2a5fdaff 100755 --- a/modules/platform/nuxeo-mail/src/test/resources/META-INF/MANIFEST.MF +++ b/modules/platform/nuxeo-mail/src/test/resources/META-INF/MANIFEST.MF @@ -1,5 +1,5 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Nuxeo Mail Services Test -Bundle-SymbolicName: org.nuxeo.mail.tst;singleton:=true +Bundle-SymbolicName: org.nuxeo.mail.test;singleton:=true Bundle-Vendor: Nuxeo diff --git a/modules/platform/nuxeo-mail/src/test/resources/OSGI-INF/test-smtp-mail-sender-contrib.xml b/modules/platform/nuxeo-mail/src/test/resources/OSGI-INF/test-smtp-mail-sender-contrib.xml new file mode 100644 index 00000000000..7f3899fe7a5 --- /dev/null +++ b/modules/platform/nuxeo-mail/src/test/resources/OSGI-INF/test-smtp-mail-sender-contrib.xml @@ -0,0 +1,13 @@ + + + + + + smtp + 127.0.0.1 + ${nuxeo.test.mail.smtp.port} + noreply@nuxeo.com + + + + diff --git a/modules/platform/nuxeo-platform-comment/nuxeo-platform-comment/pom.xml b/modules/platform/nuxeo-platform-comment/nuxeo-platform-comment/pom.xml index 7074da15657..5920b26edf0 100644 --- a/modules/platform/nuxeo-platform-comment/nuxeo-platform-comment/pom.xml +++ b/modules/platform/nuxeo-platform-comment/nuxeo-platform-comment/pom.xml @@ -20,6 +20,12 @@ org.nuxeo.ecm.platform nuxeo-platform-notification + + org.nuxeo.ecm.platform + nuxeo-platform-notification + test-jar + test + org.nuxeo.ecm.core nuxeo-core-api diff --git a/modules/platform/nuxeo-platform-comment/nuxeo-platform-comment/src/test/java/org/nuxeo/ecm/platform/comment/CommentNotificationFeature.java b/modules/platform/nuxeo-platform-comment/nuxeo-platform-comment/src/test/java/org/nuxeo/ecm/platform/comment/CommentNotificationFeature.java index e555b2936f3..33ba23d2ab1 100644 --- a/modules/platform/nuxeo-platform-comment/nuxeo-platform-comment/src/test/java/org/nuxeo/ecm/platform/comment/CommentNotificationFeature.java +++ b/modules/platform/nuxeo-platform-comment/nuxeo-platform-comment/src/test/java/org/nuxeo/ecm/platform/comment/CommentNotificationFeature.java @@ -19,6 +19,7 @@ package org.nuxeo.ecm.platform.comment; +import org.nuxeo.ecm.platform.ec.notification.NotificationFeature; import org.nuxeo.runtime.test.runner.Deploy; import org.nuxeo.runtime.test.runner.Features; import org.nuxeo.runtime.test.runner.RunnerFeature; @@ -28,9 +29,7 @@ import org.nuxeo.runtime.test.runner.RunnerFeature; * * @since 11.1 */ -@Features(CommentFeature.class) -@Deploy("org.nuxeo.ecm.platform.notification") +@Features({ CommentFeature.class, NotificationFeature.class }) @Deploy("org.nuxeo.ecm.platform.url") -@Deploy("org.nuxeo.ecm.platform.comment.tests:OSGI-INF/notification-contrib.xml") public class CommentNotificationFeature implements RunnerFeature { } diff --git a/modules/platform/nuxeo-platform-comment/nuxeo-platform-comment/src/test/java/org/nuxeo/ecm/platform/comment/TestCommentsMigrator.java b/modules/platform/nuxeo-platform-comment/nuxeo-platform-comment/src/test/java/org/nuxeo/ecm/platform/comment/TestCommentsMigrator.java index 68636272224..f9b88229ff0 100644 --- a/modules/platform/nuxeo-platform-comment/nuxeo-platform-comment/src/test/java/org/nuxeo/ecm/platform/comment/TestCommentsMigrator.java +++ b/modules/platform/nuxeo-platform-comment/nuxeo-platform-comment/src/test/java/org/nuxeo/ecm/platform/comment/TestCommentsMigrator.java @@ -84,6 +84,7 @@ import org.nuxeo.ecm.platform.comment.impl.PropertyCommentManager; import org.nuxeo.ecm.platform.comment.impl.TreeCommentManager; import org.nuxeo.ecm.platform.comment.service.CommentServiceConfig; import org.nuxeo.ecm.platform.comment.service.CommentServiceHelper; +import org.nuxeo.ecm.platform.ec.notification.NotificationFeature; import org.nuxeo.ecm.platform.notification.api.NotificationManager; import org.nuxeo.ecm.platform.relations.api.Graph; import org.nuxeo.ecm.platform.relations.api.RelationManager; @@ -104,9 +105,8 @@ import org.nuxeo.runtime.test.runner.TransactionalFeature; * @since 10.3 */ @RunWith(FeaturesRunner.class) -@Features({ CommentFeature.class, LogFeature.class, LogCaptureFeature.class }) +@Features({ CommentFeature.class, LogFeature.class, LogCaptureFeature.class, NotificationFeature.class }) @RepositoryConfig(cleanup = Granularity.METHOD) -@Deploy("org.nuxeo.ecm.platform.notification") @Deploy("org.nuxeo.ecm.relations.api") @Deploy("org.nuxeo.ecm.relations") @Deploy("org.nuxeo.ecm.relations.jena") diff --git a/modules/platform/nuxeo-platform-comment/nuxeo-platform-comment/src/test/resources/OSGI-INF/notification-contrib.xml b/modules/platform/nuxeo-platform-comment/nuxeo-platform-comment/src/test/resources/OSGI-INF/notification-contrib.xml deleted file mode 100644 index 31a1ff0e353..00000000000 --- a/modules/platform/nuxeo-platform-comment/nuxeo-platform-comment/src/test/resources/OSGI-INF/notification-contrib.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - http://localhost:8080/nuxeo/ - [Nuxeo5] - java:comp/env/Mail - - - diff --git a/modules/platform/nuxeo-platform-notification/pom.xml b/modules/platform/nuxeo-platform-notification/pom.xml index 2a9e308eb26..6c6a2ad4e31 100644 --- a/modules/platform/nuxeo-platform-notification/pom.xml +++ b/modules/platform/nuxeo-platform-notification/pom.xml @@ -127,4 +127,25 @@ + + + + org.apache.maven.plugins + maven-jar-plugin + + + + + src/test/resources/META-INF/MANIFEST.MF + + + + test-jar + + + + + + + diff --git a/modules/platform/nuxeo-platform-notification/src/main/java/org/nuxeo/ecm/platform/ec/notification/NotificationEventListener.java b/modules/platform/nuxeo-platform-notification/src/main/java/org/nuxeo/ecm/platform/ec/notification/NotificationEventListener.java index 323d3e9598a..4148a5b0eb4 100644 --- a/modules/platform/nuxeo-platform-notification/src/main/java/org/nuxeo/ecm/platform/ec/notification/NotificationEventListener.java +++ b/modules/platform/nuxeo-platform-notification/src/main/java/org/nuxeo/ecm/platform/ec/notification/NotificationEventListener.java @@ -31,9 +31,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import javax.mail.MessagingException; -import javax.mail.SendFailedException; - import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -59,6 +56,7 @@ import org.nuxeo.ecm.platform.url.DocumentViewImpl; import org.nuxeo.ecm.platform.url.api.DocumentViewCodecManager; import org.nuxeo.ecm.platform.url.codec.api.DocumentViewCodec; import org.nuxeo.ecm.platform.usermanager.UserManager; +import org.nuxeo.mail.MailException; import org.nuxeo.runtime.api.Framework; public class NotificationEventListener implements PostCommitFilteringEventListener { @@ -365,14 +363,11 @@ public class NotificationEventListener implements PostCommitFilteringEventListen mail.put(NotificationConstants.EVENT_ID_KEY, eventId); try { - emailHelper.sendmail(mail); - } catch (MessagingException e) { - String cause = ""; - if ((e instanceof SendFailedException) && (e.getCause() instanceof SendFailedException)) { - cause = " - Cause: " + e.getCause().getMessage(); - } + emailHelper.sendMailMessage(mail); + } catch (MailException e) { log.warn("Failed to send notification email to '" + email + "': " + e.getClass().getName() + ": " - + e.getMessage() + cause); + + e.getMessage()); + log.debug(e, e); } } diff --git a/modules/platform/nuxeo-platform-notification/src/main/java/org/nuxeo/ecm/platform/ec/notification/email/EmailHelper.java b/modules/platform/nuxeo-platform-notification/src/main/java/org/nuxeo/ecm/platform/ec/notification/email/EmailHelper.java index 448a508971d..63da592ec9d 100644 --- a/modules/platform/nuxeo-platform-notification/src/main/java/org/nuxeo/ecm/platform/ec/notification/email/EmailHelper.java +++ b/modules/platform/nuxeo-platform-notification/src/main/java/org/nuxeo/ecm/platform/ec/notification/email/EmailHelper.java @@ -20,49 +20,43 @@ package org.nuxeo.ecm.platform.ec.notification.email; -import java.io.IOException; -import java.io.Serializable; -import java.io.StringReader; -import java.io.StringWriter; -import java.io.Writer; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; - -import javax.mail.Message; -import javax.mail.MessagingException; -import javax.mail.Session; -import javax.mail.Transport; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeMessage; +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.mvel2.MVEL; import org.nuxeo.ecm.core.api.DocumentModel; -import org.nuxeo.ecm.core.api.NuxeoException; import org.nuxeo.ecm.platform.ec.notification.NotificationConstants; -import org.nuxeo.ecm.platform.ec.notification.service.NotificationService; import org.nuxeo.ecm.platform.ec.notification.service.NotificationServiceHelper; import org.nuxeo.ecm.platform.notification.api.NotificationManager; import org.nuxeo.ecm.platform.rendering.RenderingException; import org.nuxeo.ecm.platform.rendering.RenderingResult; import org.nuxeo.ecm.platform.rendering.RenderingService; import org.nuxeo.ecm.platform.rendering.impl.DocumentRenderingContext; +import org.nuxeo.mail.MailException; +import org.nuxeo.mail.MailMessage; +import org.nuxeo.mail.MailService; import org.nuxeo.mail.MailSessionBuilder; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.api.login.NuxeoLoginContext; -import freemarker.template.Configuration; -import freemarker.template.Template; -import freemarker.template.TemplateException; +import javax.mail.MessagingException; +import javax.mail.Session; +import java.io.IOException; +import java.io.Serializable; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; /** - * Class EmailHelper. - *

- * An email helper: + * Helper class to build some {@link MailMessage}s from {@link Map}s and send them through the + * {@link MailService}. * *

  * Hashtable mail = new Hashtable();
@@ -71,10 +65,8 @@ import freemarker.template.TemplateException;
  * mail.put("subject", "a subject");
  * mail.put("template", "a template name");
  * <p>
- * EmailHelper.sendmail(mail);
+ * EmailHelper.sendMailMessage(mail);
  * 
- * - * Currently only supports one email in to address */ public class EmailHelper { @@ -83,13 +75,26 @@ public class EmailHelper { // used for loading templates from strings private final Configuration stringCfg = new Configuration(Configuration.VERSION_2_3_0); - protected static boolean javaMailNotAvailable = false; + /** + * Sends {@link MailMessage}s. + * + * @since 2023.4 + */ + public void sendMailMessage(Map mail) { + try { + sendmail0(mail); + } catch (IOException | TemplateException | RenderingException e) { + throw new MailException(e.getMessage(), e); + } + } /** - * Static Method: sendmail(Map mail). + * Sends mails from a {@link Map}. * * @param mail A map of the settings + * @deprecated since 2023.4 because doesn't fit in a generic service. Use {@link #sendMailMessage} instead. */ + @Deprecated(since = "2023.4") public void sendmail(Map mail) throws MessagingException { try { sendmail0(mail); @@ -98,32 +103,31 @@ public class EmailHelper { } } - protected void sendmail0(Map mail) - throws MessagingException, IOException, TemplateException, RenderingException { - - Session session = getSession(); - if (javaMailNotAvailable || session == null) { - log.warn("Not sending email since JavaMail is not configured"); - return; - } - - // Construct a MimeMessage - MimeMessage msg = new MimeMessage(session); - msg.setFrom(new InternetAddress(session.getProperty("mail.from"))); + protected void sendmail0(Map mail) throws IOException, TemplateException, RenderingException { Object to = mail.get("mail.to"); if (!(to instanceof String)) { log.error("Invalid email recipient: " + to); return; } - msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse((String) to, false)); - - RenderingService rs = Framework.getService(RenderingService.class); DocumentRenderingContext context = new DocumentRenderingContext(); context.putAll(mail); context.setDocument((DocumentModel) mail.get("document")); context.put("Runtime", Framework.getRuntime()); + // Build the message. + var mailMessage = new MailMessage.Builder((String) to).subject(computeSubject(mail, context)) + .content(renderHTMLBody(mail, context), + "text/html; charset=utf-8") + .senderName(getSenderName()) + .build(); + + // Send the message. + Framework.getService(MailService.class).sendMail(mailMessage); + } + + protected String computeSubject(Map mail, DocumentRenderingContext context) + throws IOException, TemplateException, RenderingException { String customSubjectTemplate = (String) mail.get(NotificationConstants.SUBJECT_TEMPLATE_KEY); if (customSubjectTemplate == null) { String subjTemplate = (String) mail.get(NotificationConstants.SUBJECT_KEY); @@ -133,8 +137,9 @@ public class EmailHelper { templ.process(mail, out); out.flush(); - msg.setSubject(out.toString(), "UTF-8"); + return out.toString(); } else { + RenderingService rs = Framework.getService(RenderingService.class); rs.registerEngine(new NotificationsRenderingEngine(customSubjectTemplate)); try (NuxeoLoginContext loginContext = Framework.loginSystem()) { @@ -145,12 +150,14 @@ public class EmailHelper { subjectMail = (String) result.getOutcome(); } subjectMail = NotificationServiceHelper.getNotificationService().getEMailSubjectPrefix() + subjectMail; - msg.setSubject(subjectMail, "UTF-8"); + return subjectMail; } } + } - msg.setSentDate(new Date()); - + protected String renderHTMLBody(Map mail, DocumentRenderingContext context) + throws RenderingException { + RenderingService rs = Framework.getService(RenderingService.class); String template = (String) mail.get(NotificationConstants.TEMPLATE_KEY); rs.registerEngine(new NotificationsRenderingEngine(template)); @@ -166,27 +173,11 @@ public class EmailHelper { rs.unregisterEngine(template); - msg.setContent(bodyMail, "text/html; charset=utf-8"); - - // Send the message. - Transport.send(msg); + return bodyMail; } - /** - * Gets the session from the JNDI. - */ - private static Session getSession() { - if (!javaMailNotAvailable) { - // First, try to get the session from JNDI, as would be done under J2EE. - try { - NotificationService service = (NotificationService) Framework.getService(NotificationManager.class); - return MailSessionBuilder.fromJndi(service.getMailSessionJndiName()).build(); - } catch (NuxeoException ex) { - log.warn("Unable to find Java mail API", ex); - javaMailNotAvailable = true; - } - } - return null; + private static String getSenderName() { + return Framework.getService(NotificationManager.class).getMailSenderName(); } /** diff --git a/modules/platform/nuxeo-platform-notification/src/main/java/org/nuxeo/ecm/platform/ec/notification/service/GeneralSettingsDescriptor.java b/modules/platform/nuxeo-platform-notification/src/main/java/org/nuxeo/ecm/platform/ec/notification/service/GeneralSettingsDescriptor.java index 33c6cf72873..7a0a795b0c4 100644 --- a/modules/platform/nuxeo-platform-notification/src/main/java/org/nuxeo/ecm/platform/ec/notification/service/GeneralSettingsDescriptor.java +++ b/modules/platform/nuxeo-platform-notification/src/main/java/org/nuxeo/ecm/platform/ec/notification/service/GeneralSettingsDescriptor.java @@ -39,6 +39,9 @@ public class GeneralSettingsDescriptor { @XNode("mailSessionJndiName") protected String mailSessionJndiName; + @XNode("mailSenderName") + protected String mailSenderName; + public String getEMailSubjectPrefix() { return eMailSubjectPrefix; } @@ -47,8 +50,21 @@ public class GeneralSettingsDescriptor { return serverPrefix; } + /** + * @deprecated since 2023.4 use {@link #getMailSenderName()} instead. + */ + @Deprecated(since = "2023.4") public String getMailSessionJndiName() { return mailSessionJndiName; } + /** + * Gets the name of the {@link org.nuxeo.mail.MailSender} to use. + * + * @since 2023.4 + */ + public String getMailSenderName() { + return mailSenderName; + } + } diff --git a/modules/platform/nuxeo-platform-notification/src/main/java/org/nuxeo/ecm/platform/ec/notification/service/NotificationService.java b/modules/platform/nuxeo-platform-notification/src/main/java/org/nuxeo/ecm/platform/ec/notification/service/NotificationService.java index 93be296c476..6238647a9a4 100644 --- a/modules/platform/nuxeo-platform-notification/src/main/java/org/nuxeo/ecm/platform/ec/notification/service/NotificationService.java +++ b/modules/platform/nuxeo-platform-notification/src/main/java/org/nuxeo/ecm/platform/ec/notification/service/NotificationService.java @@ -21,6 +21,7 @@ package org.nuxeo.ecm.platform.ec.notification.service; import static java.lang.Boolean.TRUE; import static java.util.Collections.singletonList; import static java.util.stream.Collectors.toList; +import static org.nuxeo.mail.MailServiceImpl.DEFAULT_SENDER; import java.io.Serializable; import java.net.URL; @@ -31,8 +32,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import javax.mail.MessagingException; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.core.api.CoreInstance; @@ -63,6 +62,10 @@ import org.nuxeo.ecm.platform.notification.api.NotificationRegistry; import org.nuxeo.ecm.platform.url.DocumentViewImpl; import org.nuxeo.ecm.platform.url.api.DocumentView; import org.nuxeo.ecm.platform.url.api.DocumentViewCodecManager; +import org.nuxeo.mail.MailConstants; +import org.nuxeo.mail.MailException; +import org.nuxeo.mail.MailService; +import org.nuxeo.mail.MailServiceImpl; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.model.ComponentContext; import org.nuxeo.runtime.model.ComponentName; @@ -108,6 +111,8 @@ public class NotificationService extends DefaultComponent implements Notificatio protected NotificationListenerVetoRegistry notificationVetoRegistry; + protected String senderName = DEFAULT_SENDER; + @Override @SuppressWarnings("unchecked") public T getAdapter(Class adapter) { @@ -137,6 +142,22 @@ public class NotificationService extends DefaultComponent implements Notificatio notificationVetoRegistry = null; } + @Override + public void start(ComponentContext context) { + super.start(context); + if (generalSettings.getMailSenderName() == null) { + var jndiSessionName = generalSettings.getMailSessionJndiName(); + if (!jndiSessionName.equals(MailConstants.DEFAULT_MAIL_JNDI_NAME)) { + log.warn( + "Your GeneralSettingsDescriptor has been contributed with a custom mailSessionJndiName. This field is now deprecated. Please use mailSenderName and contribute a MailSenderDescriptor"); + senderName = ((MailServiceImpl) Framework.getService(MailService.class)).registerJndiSMTPSender( + jndiSessionName); + } // else keep default + } else { + senderName = generalSettings.getMailSenderName(); + } + } + @Override public void registerExtension(Extension extension) { log.info("Registering notification extension"); @@ -364,10 +385,19 @@ public class NotificationService extends DefaultComponent implements Notificatio return generalSettings.getEMailSubjectPrefix(); } + /** + * @deprecated since 2023.4 use {@link #getMailSenderName()} instead. + */ + @Deprecated(since = "2023.4") public String getMailSessionJndiName() { return generalSettings.getMailSessionJndiName(); } + @Override + public String getMailSenderName() { + return senderName; + } + @Override public Notification getNotificationByName(String selectedNotification) { List listNotif = notificationRegistry.getNotifications(); @@ -410,8 +440,8 @@ public class NotificationService extends DefaultComponent implements Notificatio infoMap.put("template", mailTemplate); try { - emailHelper.sendmail(infoMap); - } catch (MessagingException e) { + emailHelper.sendMailMessage(infoMap); + } catch (MailException e) { throw new NuxeoException("Failed to send notification email ", e); } } @@ -439,8 +469,8 @@ public class NotificationService extends DefaultComponent implements Notificatio for (String to : sendTo) { infoMap.put("mail.to", to); try { - emailHelper.sendmail(infoMap); - } catch (MessagingException e) { + emailHelper.sendMailMessage(infoMap); + } catch (MailException e) { log.debug("Failed to send notification email " + e); } } diff --git a/modules/platform/nuxeo-platform-notification/src/main/java/org/nuxeo/ecm/platform/notification/api/NotificationManager.java b/modules/platform/nuxeo-platform-notification/src/main/java/org/nuxeo/ecm/platform/notification/api/NotificationManager.java index 2197ec1f411..8828e0e0a7c 100644 --- a/modules/platform/nuxeo-platform-notification/src/main/java/org/nuxeo/ecm/platform/notification/api/NotificationManager.java +++ b/modules/platform/nuxeo-platform-notification/src/main/java/org/nuxeo/ecm/platform/notification/api/NotificationManager.java @@ -48,6 +48,15 @@ public interface NotificationManager { */ List getUsersSubscribedToNotificationOnDocument(String notification, DocumentModel doc); + /** + * Returns the name of the {@link org.nuxeo.mail.MailSender} that will handle notification {@link org.nuxeo.mail.MailMessage}s + * + * @since 2023.4 + */ + default String getMailSenderName() { + throw new UnsupportedOperationException("Not implemented"); + } + /** * Called when a user subscribes to a notification. */ diff --git a/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/NotificationFeature.java b/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/NotificationFeature.java new file mode 100644 index 00000000000..59953d1fc85 --- /dev/null +++ b/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/NotificationFeature.java @@ -0,0 +1,31 @@ +/* + * (C) Copyright 2023 Nuxeo (http://nuxeo.com/) and others. + * + * 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.nuxeo.ecm.platform.ec.notification; + +import org.nuxeo.runtime.test.runner.Deploy; +import org.nuxeo.runtime.test.runner.RunnerFeature; + +/** + * Feature for the notification stack. + * + * @since 2023.4 + */ +@Deploy("org.nuxeo.ecm.platform.notification") +@Deploy("org.nuxeo.ecm.platform.notification.tests:default-general-settings-contrib.xml") +public class NotificationFeature implements RunnerFeature { +} diff --git a/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/TestEmailNotification.java b/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/TestEmailNotification.java index 1dac088c702..5a26915a011 100644 --- a/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/TestEmailNotification.java +++ b/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/TestEmailNotification.java @@ -59,9 +59,8 @@ import org.nuxeo.runtime.test.runner.TransactionalFeature; * @since 11.1 */ @RunWith(FeaturesRunner.class) -@Features({ PlatformFeature.class, SmtpMailServerFeature.class }) +@Features({ PlatformFeature.class, SmtpMailServerFeature.class, NotificationFeature.class }) @Deploy("org.nuxeo.ecm.platform.url") -@Deploy("org.nuxeo.ecm.platform.notification") @Deploy("org.nuxeo.ecm.platform.notification.tests:OSGI-INF/notification-event-listener-contrib.xml") public class TestEmailNotification { @@ -133,7 +132,7 @@ public class TestEmailNotification { assertEquals(1, emailsResult.getMails().size()); SmtpMailServerFeature.MailMessage mailMessage = emailsResult.getMails().get(0); // check the subject - assertEquals(String.format("[Dummy]Notification on the document '%s'", documentModel.getTitle()), + assertEquals(String.format("[Nuxeo]Notification on the document '%s'", documentModel.getTitle()), mailMessage.getSubject()); // check the text content diff --git a/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/TestNotificationManager.java b/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/TestNotificationManager.java index fac29cab2a5..c00a01f5520 100644 --- a/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/TestNotificationManager.java +++ b/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/TestNotificationManager.java @@ -39,7 +39,6 @@ import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.NuxeoPrincipal; import org.nuxeo.ecm.core.test.CoreFeature; import org.nuxeo.ecm.platform.notification.api.NotificationManager; -import org.nuxeo.runtime.test.runner.Deploy; import org.nuxeo.runtime.test.runner.Features; import org.nuxeo.runtime.test.runner.FeaturesRunner; import org.nuxeo.runtime.test.runner.TransactionalFeature; @@ -48,8 +47,7 @@ import org.nuxeo.runtime.test.runner.TransactionalFeature; * @since 9.1 */ @RunWith(FeaturesRunner.class) -@Features(CoreFeature.class) -@Deploy("org.nuxeo.ecm.platform.notification") +@Features({ CoreFeature.class, NotificationFeature.class }) public class TestNotificationManager { @Inject @@ -140,7 +138,8 @@ public class TestNotificationManager { repositoryName); assertEquals(singletonList(file), subscribedDocuments); - // add subscriptions to proxy on a different notification to ensure it's not inherited from source doc or version + // add subscriptions to proxy on a different notification to ensure it's not inherited from source doc or + // version notificationManager.addSubscription(prefixedPrincipalName, "notification2", publishedDocument, FALSE, principal, "notification2"); transactionalFeature.nextTransaction(); @@ -148,7 +147,8 @@ public class TestNotificationManager { // check that we now have published document but not the version subscribedDocuments = notificationManager.getSubscribedDocuments(prefixedPrincipalName, repositoryName); subscribedDocuments.sort(comparing(DocumentModel::getPathAsString)); - List subscriptions = notificationManager.getSubscriptionsForUserOnDocument(prefixedPrincipalName, publishedDocument); + List subscriptions = notificationManager.getSubscriptionsForUserOnDocument(prefixedPrincipalName, + publishedDocument); assertEquals(1, subscriptions.size()); assertEquals(asList(file, publishedDocument), subscribedDocuments); diff --git a/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/TestRegisterNotificationService.java b/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/TestRegisterNotificationService.java index 32442ee2a63..785b6ddf435 100644 --- a/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/TestRegisterNotificationService.java +++ b/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/TestRegisterNotificationService.java @@ -21,11 +21,8 @@ package org.nuxeo.ecm.platform.ec.notification; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.nuxeo.mail.MailConstants.DEFAULT_MAIL_JNDI_NAME; +import static org.nuxeo.mail.MailServiceImpl.DEFAULT_SENDER; -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; import java.io.Serializable; import java.net.URL; import java.util.Collection; @@ -35,27 +32,28 @@ import java.util.Map; import javax.inject.Inject; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.nuxeo.common.utils.FileUtils; import org.nuxeo.ecm.platform.ec.notification.email.EmailHelper; import org.nuxeo.ecm.platform.ec.notification.service.NotificationService; import org.nuxeo.ecm.platform.notification.api.Notification; import org.nuxeo.ecm.platform.notification.api.NotificationManager; import org.nuxeo.runtime.api.Framework; -import org.nuxeo.runtime.osgi.OSGiRuntimeService; import org.nuxeo.runtime.test.runner.Deploy; import org.nuxeo.runtime.test.runner.Features; import org.nuxeo.runtime.test.runner.FeaturesRunner; import org.nuxeo.runtime.test.runner.HotDeployer; import org.nuxeo.runtime.test.runner.RuntimeFeature; +import org.nuxeo.runtime.test.runner.WithFrameworkProperty; /** * @author Ruslan Spivak */ @RunWith(FeaturesRunner.class) -@Features(RuntimeFeature.class) +@Features({ RuntimeFeature.class, NotificationFeature.class }) +@Deploy("org.nuxeo.mail") +@WithFrameworkProperty(name = "org.nuxeo.ecm.notification.serverPrefix", value = "testServerPrefix") +@WithFrameworkProperty(name = "org.nuxeo.ecm.notification.eMailSubjectPrefix", value = "testSubjectPrefix") public class TestRegisterNotificationService { @@ -64,15 +62,6 @@ public class TestRegisterNotificationService { @Inject protected HotDeployer hotDeployer; - @Before - public void setUp() throws Exception { - File propertiesFile = FileUtils.getResourceFileFromContext("notifications.properties"); - try (InputStream notificationsProperties = new FileInputStream(propertiesFile)) { - ((OSGiRuntimeService) Framework.getRuntime()).loadProperties(notificationsProperties); - } - hotDeployer.deploy("org.nuxeo.ecm.platform.notification:OSGI-INF/NotificationService.xml"); - } - @Test @Deploy("org.nuxeo.ecm.platform.notification.tests:notification-contrib.xml") public void testRegistration() { @@ -155,19 +144,19 @@ public class TestRegisterNotificationService { public void testExpandVarsInGeneralSettings() throws Exception { hotDeployer.deploy("org.nuxeo.ecm.platform.notification.tests:notification-contrib.xml"); + // these should not be altered assertEquals("http://localhost:8080/nuxeo/", getService().getServerUrlPrefix()); - assertEquals("[Nuxeo5]", getService().getEMailSubjectPrefix()); - - // this one should not be expanded - assertEquals(DEFAULT_MAIL_JNDI_NAME, getService().getMailSessionJndiName()); + assertEquals("[Nuxeo]", getService().getEMailSubjectPrefix()); + assertEquals(DEFAULT_SENDER, getService().getMailSenderName()); hotDeployer.deploy("org.nuxeo.ecm.platform.notification.tests:notification-contrib-overridden.xml"); + // these should be expanded assertEquals("http://testServerPrefix/nuxeo", getService().getServerUrlPrefix()); assertEquals("testSubjectPrefix", getService().getEMailSubjectPrefix()); // this one should not be expanded - assertEquals("${not.existing.property}", getService().getMailSessionJndiName()); + assertEquals("${not.existing.property}", getService().getMailSenderName()); } @Test @@ -176,7 +165,7 @@ public class TestRegisterNotificationService { public void testVetoRegistration() { Collection vetos = getService().getNotificationVetos(); - assertEquals(2, vetos.size()); + assertEquals(3, vetos.size()); assertEquals("org.nuxeo.ecm.platform.ec.notification.veto.NotificationVeto1", getService().getNotificationListenerVetoRegistry().getVeto("veto1").getClass().getCanonicalName()); assertEquals("org.nuxeo.ecm.platform.ec.notification.veto.NotificationVeto20", diff --git a/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/TestRenderingService.java b/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/TestRenderingService.java index 14492f49f6e..63c23ea383b 100644 --- a/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/TestRenderingService.java +++ b/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/TestRenderingService.java @@ -39,8 +39,7 @@ import org.nuxeo.runtime.test.runner.Features; import org.nuxeo.runtime.test.runner.FeaturesRunner; @RunWith(FeaturesRunner.class) -@Features(CoreFeature.class) -@Deploy("org.nuxeo.ecm.platform.notification") +@Features({ CoreFeature.class, NotificationFeature.class }) @Deploy("org.nuxeo.ecm.platform.notification.tests:notification-contrib.xml") public class TestRenderingService { diff --git a/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/UserSubscriptionAdapterTest.java b/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/UserSubscriptionAdapterTest.java index 19c77cbbd6d..9897e238c81 100644 --- a/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/UserSubscriptionAdapterTest.java +++ b/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/UserSubscriptionAdapterTest.java @@ -35,15 +35,13 @@ import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.test.CoreFeature; import org.nuxeo.ecm.core.test.annotations.Granularity; import org.nuxeo.ecm.core.test.annotations.RepositoryConfig; -import org.nuxeo.runtime.test.runner.Deploy; import org.nuxeo.runtime.test.runner.Features; import org.nuxeo.runtime.test.runner.FeaturesRunner; import com.google.inject.Inject; @RunWith(FeaturesRunner.class) -@Features(CoreFeature.class) -@Deploy("org.nuxeo.ecm.platform.notification") +@Features({ CoreFeature.class, NotificationFeature.class }) @RepositoryConfig(cleanup = Granularity.METHOD) public class UserSubscriptionAdapterTest { diff --git a/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/operations/SubscribeAndUnsubscribeTest.java b/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/operations/SubscribeAndUnsubscribeTest.java index 688c3c2f80c..4eb843450b7 100644 --- a/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/operations/SubscribeAndUnsubscribeTest.java +++ b/modules/platform/nuxeo-platform-notification/src/test/java/org/nuxeo/ecm/platform/ec/notification/operations/SubscribeAndUnsubscribeTest.java @@ -40,6 +40,7 @@ import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.DocumentModelList; import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl; import org.nuxeo.ecm.platform.ec.notification.NotificationConstants; +import org.nuxeo.ecm.platform.ec.notification.NotificationFeature; import org.nuxeo.ecm.platform.ec.notification.SubscriptionAdapter; import org.nuxeo.ecm.platform.ec.notification.automation.SubscribeOperation; import org.nuxeo.ecm.platform.ec.notification.automation.UnsubscribeOperation; @@ -52,9 +53,8 @@ import org.nuxeo.runtime.test.runner.FeaturesRunner; * @since 8.10 */ @RunWith(FeaturesRunner.class) -@Features(PlatformFeature.class) +@Features({ PlatformFeature.class, NotificationFeature.class }) @Deploy("org.nuxeo.ecm.automation.core") -@Deploy("org.nuxeo.ecm.platform.notification") public class SubscribeAndUnsubscribeTest { protected DocumentModel testWorkspace; diff --git a/modules/platform/nuxeo-platform-notification/src/test/resources/OSGI-INF/notification-event-listener-contrib.xml b/modules/platform/nuxeo-platform-notification/src/test/resources/OSGI-INF/notification-event-listener-contrib.xml index da4c53adc18..9ffa879e90d 100644 --- a/modules/platform/nuxeo-platform-notification/src/test/resources/OSGI-INF/notification-event-listener-contrib.xml +++ b/modules/platform/nuxeo-platform-notification/src/test/resources/OSGI-INF/notification-event-listener-contrib.xml @@ -1,15 +1,6 @@ - - - http://localhost:8080/nuxeo/ - [Dummy] - java:comp/env/Mail - - - diff --git a/modules/platform/nuxeo-platform-notification/src/test/resources/default-general-settings-contrib.xml b/modules/platform/nuxeo-platform-notification/src/test/resources/default-general-settings-contrib.xml new file mode 100644 index 00000000000..7051ef8a8cb --- /dev/null +++ b/modules/platform/nuxeo-platform-notification/src/test/resources/default-general-settings-contrib.xml @@ -0,0 +1,12 @@ + + + + + + http://localhost:8080/nuxeo/ + [Nuxeo] + default + + + + diff --git a/modules/platform/nuxeo-platform-notification/src/test/resources/notification-contrib-disabled.xml b/modules/platform/nuxeo-platform-notification/src/test/resources/notification-contrib-disabled.xml index 8e812bccc95..6062c211980 100644 --- a/modules/platform/nuxeo-platform-notification/src/test/resources/notification-contrib-disabled.xml +++ b/modules/platform/nuxeo-platform-notification/src/test/resources/notification-contrib-disabled.xml @@ -22,13 +22,4 @@