You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ol...@apache.org on 2019/12/15 18:19:39 UTC
[sling-org-apache-sling-commons-messaging-mail] 03/03: SLING-8920
Provide a simple API and implementation to build and send mails
This is an automated email from the ASF dual-hosted git repository.
olli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-commons-messaging-mail.git
commit fd1350d97590bb335e57e19c3ad8c208670fe316
Author: Oliver Lietz <ol...@apache.org>
AuthorDate: Sun Dec 15 19:18:17 2019 +0100
SLING-8920 Provide a simple API and implementation to build and send mails
---
README.md | 62 ++-
pom.xml | 146 +++++--
...lServiceConfiguration.java => MailService.java} | 26 +-
.../commons/messaging/mail/MessageBuilder.java | 85 ++++
...ceConfiguration.java => MessageIdProvider.java} | 22 +-
.../messaging/mail/internal/SimpleMailService.java | 124 ++++--
.../internal/SimpleMailServiceConfiguration.java | 66 ++-
.../mail/internal/SimpleMessageBuilder.java | 473 +++++++++++++++++++++
.../mail/internal/SimpleMessageIdProvider.java | 76 ++++
...a => SimpleMessageIdProviderConfiguration.java} | 21 +-
.../messaging/mail/it/tests/MailTestSupport.java | 144 +++++++
.../mail/it/tests/SimpleMailServiceIT.java | 414 ++++++++++++++++++
src/test/resources/SupportApache-small.png | Bin 0 -> 96596 bytes
src/test/resources/password | 1 +
src/test/resources/sling.png | Bin 0 -> 2957 bytes
src/test/resources/template-inlines.html | 38 ++
src/test/resources/template.html | 35 ++
src/test/resources/template.txt | 3 +
18 files changed, 1618 insertions(+), 118 deletions(-)
diff --git a/README.md b/README.md
index a055395..3d9141a 100644
--- a/README.md
+++ b/README.md
@@ -6,19 +6,57 @@
This module is part of the [Apache Sling](https://sling.apache.org) project.
-Provide an OSGi Configuration for `SimpleMailBuilder` or a custom `MailBuilder` to send messages using [Apache Commons Email](https://commons.apache.org/proper/commons-email/).
+This module provides a simple layer on top of [Jakarta Mail](https://eclipse-ee4j.github.io/mail/) (former [JavaMail](https://javaee.github.io/javamail/)) including a message builder and a service to send mails via SMTPS.
-To extend or override `SimpleMailBuilder`s configuration call `MessageService#send(String, String, Map):Future<Result>` and supply a configuration map `mail` within the third parameter:
+* Mail Service: sends MIME messages.
+* Message Builder: builds plain text and HTML messages with attachments and inline images
+* Message ID Provider: allows overwriting default message IDs by custom ones
+
+
+## Example
```
-{
- "mail" : {
- "mail.subject": <String>,
- "mail.from": <String>,
- "mail.smtp.hostname": <String>,
- "mail.smtp.port": <int>,
- "mail.smtp.username": <String>,
- "mail.smtp.password": <String>
- }
-}
+ @Reference
+ MailService mailService;
+
+ String subject = "Rudy, A Message to You";
+ String text = "Stop your messing around\nBetter think of your future\nTime you straighten right out\nCreating problems in town\n…";
+ String html = […];
+ byte[] attachment = […];
+ byte[] inline = […];
+
+ MimeMessage message = mailService.getMessageBuilder()
+ .from("dandy.livingstone@kingston.jamaica.example.net", "Dandy Livingstone")
+ .to("the.specials@coventry.england.example.net", "The Specials")
+ .replyTo("rocksteady@jamaica.example.net");
+ .subject(subject)
+ .text(text)
+ .html(html)
+ .attachment(attachment, "image/png", "attachment.png")
+ .inline(inline, "image/png", "inline")
+ .build();
+
+ mailService.sendMessage(message);
```
+
+
+## Integration Tests
+
+Integration tests require a running SMTP server. By default a [GreenMail](http://www.icegreen.com/greenmail/) server is started.
+
+An external SMTP server for validating messages with real mail clients can be used by setting required properties:
+
+ mvn clean install\
+ -Dsling.test.mail.smtps.server.external=true\
+ -Dsling.test.mail.smtps.from=envelope-from@example.org\
+ -Dsling.test.mail.smtps.host=localhost\
+ -Dsling.test.mail.smtps.port=465\
+ -Dsling.test.mail.smtps.username=username\
+ -Dsling.test.mail.smtps.password=password\
+ -Dsling.test.mail.from.address=from@example.org\
+ -Dsling.test.mail.from.name=From\ Sender\
+ -Dsling.test.mail.to.address=to@example.org\
+ -Dsling.test.mail.to.name=To\ Recipient\
+ -Dsling.test.mail.replyTo.address=replyto@example.org\
+ -Dsling.test.mail.replyTo.name=Reply\ To
+
diff --git a/pom.xml b/pom.xml
index 983cc59..4a2baf2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -32,13 +32,13 @@
<version>0.0.1-SNAPSHOT</version>
<name>Apache Sling Commons Messaging Mail</name>
- <description>Messaging service using Commons Email to send mails.</description>
+ <description>Send mails via SMTPS</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<sling.java.version>8</sling.java.version>
- <org.ops4j.pax.exam.version>4.9.1</org.ops4j.pax.exam.version>
+ <org.ops4j.pax.exam.version>4.13.1</org.ops4j.pax.exam.version>
</properties>
<scm>
@@ -61,6 +61,16 @@
<artifactId>depends-maven-plugin</artifactId>
</plugin>
<plugin>
+ <groupId>org.apache.rat</groupId>
+ <artifactId>apache-rat-plugin</artifactId>
+ <configuration>
+ <excludes combine.children="append">
+ <exclude>**/*.txt</exclude>
+ <exclude>src/test/resources/password</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
@@ -72,6 +82,7 @@
</execution>
</executions>
<configuration>
+ <redirectTestOutputToFile>true</redirectTestOutputToFile>
<systemProperties>
<property>
<name>bundle.filename</name>
@@ -84,34 +95,45 @@
</build>
<dependencies>
- <!-- javax -->
+ <!-- javax/jakarta -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<scope>test</scope>
</dependency>
<dependency>
- <groupId>javax.mail</groupId>
- <artifactId>javax.mail-api</artifactId>
- <version>1.5.5</version>
+ <groupId>jakarta.mail</groupId>
+ <artifactId>jakarta.mail-api</artifactId>
+ <version>1.6.4</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.servicemix.specs</groupId>
+ <artifactId>org.apache.servicemix.specs.activation-api-1.1</artifactId>
+ <version>2.9.0</version>
<scope>provided</scope>
</dependency>
<!-- Sun -->
<dependency>
<groupId>com.sun.mail</groupId>
- <artifactId>javax.mail</artifactId>
- <version>1.5.5</version>
+ <artifactId>jakarta.mail</artifactId>
+ <version>1.6.4</version>
<scope>provided</scope>
</dependency>
<!-- OSGi -->
<dependency>
<groupId>org.osgi</groupId>
- <artifactId>osgi.core</artifactId>
+ <artifactId>org.osgi.annotation.versioning</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
- <artifactId>org.osgi.annotation.versioning</artifactId>
+ <artifactId>osgi.cmpn</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
@@ -126,34 +148,35 @@
</dependency>
<!-- Apache Commons -->
<dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-email</artifactId>
- <version>1.4</version>
- <scope>provided</scope>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>2.5</version>
+ <scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
- <version>3.4</version>
+ <version>3.9</version>
+ <scope>provided</scope>
</dependency>
- <!-- Apache Felix -->
<dependency>
- <groupId>org.apache.felix</groupId>
- <artifactId>org.apache.felix.configadmin</artifactId>
- <version>1.8.8</version>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-email</artifactId>
+ <version>1.5</version>
<scope>test</scope>
</dependency>
+ <!-- Apache Felix -->
<dependency>
<groupId>org.apache.felix</groupId>
- <artifactId>org.apache.felix.scr</artifactId>
- <version>2.0.2</version>
+ <artifactId>org.apache.felix.framework</artifactId>
+ <version>6.0.3</version>
<scope>test</scope>
</dependency>
<!-- Apache Sling -->
<dependency>
<groupId>org.apache.sling</groupId>
- <artifactId>org.apache.sling.commons.threads</artifactId>
- <version>3.2.6</version>
+ <artifactId>org.apache.sling.commons.crypto</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -162,6 +185,45 @@
<version>0.0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.threads</artifactId>
+ <version>3.2.6</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.testing.paxexam</artifactId>
+ <version>3.1.0</version>
+ <scope>test</scope>
+ </dependency>
+ <!-- Jasypt -->
+ <dependency>
+ <groupId>org.apache.servicemix.bundles</groupId>
+ <artifactId>org.apache.servicemix.bundles.jasypt</artifactId>
+ <version>1.9.3_1</version>
+ <scope>test</scope>
+ </dependency>
+ <!-- Google -->
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>28.1-jre</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>failureaccess</artifactId>
+ <version>1.0.1</version>
+ <scope>test</scope>
+ </dependency>
+ <!-- Thymeleaf -->
+ <dependency>
+ <groupId>org.thymeleaf</groupId>
+ <artifactId>thymeleaf</artifactId>
+ <version>3.0.11.RELEASE</version>
+ <scope>test</scope>
+ </dependency>
<!-- logging -->
<dependency>
<groupId>org.slf4j</groupId>
@@ -173,7 +235,7 @@
<artifactId>slf4j-simple</artifactId>
<scope>test</scope>
</dependency>
- <!-- JSR 305-->
+ <!-- nullability -->
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
@@ -186,6 +248,24 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>com.google.truth</groupId>
+ <artifactId>truth</artifactId>
+ <version>1.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency> <!-- truth dep -->
+ <groupId>com.googlecode.java-diff-utils</groupId>
+ <artifactId>diffutils</artifactId>
+ <version>1.3.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.icegreen</groupId>
+ <artifactId>greenmail</artifactId>
+ <version>1.5.11</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-container-forked</artifactId>
<version>${org.ops4j.pax.exam.version}</version>
@@ -206,31 +286,19 @@
<dependency>
<groupId>org.ops4j.pax.url</groupId>
<artifactId>pax-url-aether</artifactId>
- <version>2.4.6</version>
+ <version>2.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.url</groupId>
<artifactId>pax-url-reference</artifactId>
- <version>2.4.6</version>
+ <version>2.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.url</groupId>
<artifactId>pax-url-wrap</artifactId>
- <version>2.4.6</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.felix</groupId>
- <artifactId>org.apache.felix.framework</artifactId>
- <version>5.4.0</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.subethamail</groupId>
- <artifactId>subethasmtp</artifactId>
- <version>3.1.7</version>
+ <version>2.6.2</version>
<scope>test</scope>
</dependency>
</dependencies>
diff --git a/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailServiceConfiguration.java b/src/main/java/org/apache/sling/commons/messaging/mail/MailService.java
similarity index 57%
copy from src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailServiceConfiguration.java
copy to src/main/java/org/apache/sling/commons/messaging/mail/MailService.java
index 493041b..258b619 100644
--- a/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailServiceConfiguration.java
+++ b/src/main/java/org/apache/sling/commons/messaging/mail/MailService.java
@@ -16,21 +16,21 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.sling.commons.messaging.mail.internal;
+package org.apache.sling.commons.messaging.mail;
-import org.osgi.service.metatype.annotations.AttributeDefinition;
-import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import java.util.concurrent.CompletableFuture;
-@ObjectClassDefinition(
- name = "Apache Sling Commons Messaging Mail “Simple Mail Service”",
- description = "simple mail service for Sling Commons Messaging"
-)
-@interface SimpleMailServiceConfiguration {
+import javax.mail.internet.MimeMessage;
- @AttributeDefinition(
- name = "ThreadPool name",
- description = "name of the ThreadPool to use for sending mails"
- )
- String threadpoolName() default "default";
+import org.apache.sling.commons.messaging.MessageService;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.annotation.versioning.ProviderType;
+
+@ProviderType
+public interface MailService extends MessageService<MimeMessage> {
+
+ @NotNull MessageBuilder getMessageBuilder();
+
+ @NotNull CompletableFuture<MimeMessage> sendMessage(@NotNull final MimeMessage message);
}
diff --git a/src/main/java/org/apache/sling/commons/messaging/mail/MessageBuilder.java b/src/main/java/org/apache/sling/commons/messaging/mail/MessageBuilder.java
new file mode 100644
index 0000000..a8f4af3
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/messaging/mail/MessageBuilder.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.sling.commons.messaging.mail;
+
+import javax.mail.Header;
+import javax.mail.MessagingException;
+import javax.mail.internet.AddressException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.InternetHeaders;
+import javax.mail.internet.MimeMessage;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.osgi.annotation.versioning.ProviderType;
+
+@ProviderType
+public interface MessageBuilder {
+
+ @NotNull MessageBuilder header(@NotNull final String name, @Nullable final String value);
+
+ @NotNull MessageBuilder headers(@NotNull final InternetHeaders headers);
+
+ @NotNull MessageBuilder from(@NotNull final InternetAddress from);
+
+ @NotNull MessageBuilder from(@NotNull final String address) throws AddressException;
+
+ @NotNull MessageBuilder from(@NotNull final String address, @NotNull final String name) throws AddressException;
+
+ @NotNull MessageBuilder to(@NotNull final InternetAddress to);
+
+ @NotNull MessageBuilder to(@NotNull final String address) throws AddressException;
+
+ @NotNull MessageBuilder to(@NotNull final String address, @NotNull final String name) throws AddressException;
+
+ @NotNull MessageBuilder cc(@NotNull final InternetAddress cc);
+
+ @NotNull MessageBuilder cc(@NotNull final String address) throws AddressException;
+
+ @NotNull MessageBuilder cc(@NotNull final String address, @NotNull final String name) throws AddressException;
+
+ @NotNull MessageBuilder bcc(@NotNull final InternetAddress bcc);
+
+ @NotNull MessageBuilder bcc(@NotNull final String address) throws AddressException;
+
+ @NotNull MessageBuilder bcc(@NotNull final String address, final String name) throws AddressException;
+
+ @NotNull MessageBuilder replyTo(@NotNull final InternetAddress replyTo);
+
+ @NotNull MessageBuilder replyTo(@NotNull final String address) throws AddressException;
+
+ @NotNull MessageBuilder replyTo(@NotNull final String address, final String name) throws AddressException;
+
+ @NotNull MessageBuilder subject(@NotNull final String subject);
+
+ @NotNull MessageBuilder text(@NotNull final String text);
+
+ @NotNull MessageBuilder html(@NotNull final String html);
+
+ @NotNull MessageBuilder attachment(@NotNull final byte[] content, @NotNull final String type, @NotNull final String filename);
+
+ @NotNull MessageBuilder attachment(@NotNull final byte[] content, @NotNull final String type, @NotNull final String filename, @Nullable Header[] headers);
+
+ @NotNull MessageBuilder inline(@NotNull final byte[] content, @NotNull final String type, @NotNull final String cid);
+
+ @NotNull MessageBuilder inline(@NotNull final byte[] content, @NotNull final String type, @NotNull final String cid, @Nullable Header[] headers);
+
+ @NotNull MimeMessage build() throws MessagingException;
+
+}
diff --git a/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailServiceConfiguration.java b/src/main/java/org/apache/sling/commons/messaging/mail/MessageIdProvider.java
similarity index 57%
copy from src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailServiceConfiguration.java
copy to src/main/java/org/apache/sling/commons/messaging/mail/MessageIdProvider.java
index 493041b..4b0eb5b 100644
--- a/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailServiceConfiguration.java
+++ b/src/main/java/org/apache/sling/commons/messaging/mail/MessageIdProvider.java
@@ -16,21 +16,17 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.sling.commons.messaging.mail.internal;
+package org.apache.sling.commons.messaging.mail;
-import org.osgi.service.metatype.annotations.AttributeDefinition;
-import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
-@ObjectClassDefinition(
- name = "Apache Sling Commons Messaging Mail “Simple Mail Service”",
- description = "simple mail service for Sling Commons Messaging"
-)
-@interface SimpleMailServiceConfiguration {
+import org.jetbrains.annotations.NotNull;
+import org.osgi.annotation.versioning.ProviderType;
- @AttributeDefinition(
- name = "ThreadPool name",
- description = "name of the ThreadPool to use for sending mails"
- )
- String threadpoolName() default "default";
+@ProviderType
+public interface MessageIdProvider {
+
+ @NotNull String getMessageId(@NotNull final MimeMessage message) throws MessagingException;
}
diff --git a/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailService.java b/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailService.java
index 20031f1..1e73eab 100644
--- a/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailService.java
+++ b/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailService.java
@@ -18,21 +18,23 @@
*/
package org.apache.sling.commons.messaging.mail.internal;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Map;
+import java.util.List;
+import java.util.Properties;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.event.TransportListener;
+import javax.mail.internet.MimeMessage;
-import org.apache.commons.mail.Email;
-import org.apache.commons.mail.EmailException;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.commons.crypto.CryptoService;
import org.apache.sling.commons.messaging.MessageService;
-import org.apache.sling.commons.messaging.Result;
-import org.apache.sling.commons.messaging.mail.MailBuilder;
-import org.apache.sling.commons.messaging.mail.MailResult;
-import org.apache.sling.commons.messaging.mail.MailUtil;
+import org.apache.sling.commons.messaging.mail.MailService;
+import org.apache.sling.commons.messaging.mail.MessageBuilder;
+import org.apache.sling.commons.messaging.mail.MessageIdProvider;
import org.apache.sling.commons.threads.ThreadPool;
import org.apache.sling.commons.threads.ThreadPoolManager;
import org.jetbrains.annotations.NotNull;
@@ -50,23 +52,28 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component(
- service = MessageService.class,
+ service = {
+ MessageService.class,
+ MailService.class
+ },
property = {
- Constants.SERVICE_DESCRIPTION + "=Service to send messages by mail.",
- Constants.SERVICE_VENDOR + "=The Apache Software Foundation"
+ Constants.SERVICE_DESCRIPTION + "=Apache Sling Commons Messaging Mail – Simple Mail Service",
+ Constants.SERVICE_VENDOR + "=The Apache Software Foundation",
+ "protocol=SMTPS"
}
)
@Designate(
- ocd = SimpleMailServiceConfiguration.class
+ ocd = SimpleMailServiceConfiguration.class,
+ factory = true
)
-public class SimpleMailService implements MessageService {
+public class SimpleMailService implements MailService {
@Reference(
- cardinality = ReferenceCardinality.MANDATORY,
+ cardinality = ReferenceCardinality.OPTIONAL,
policy = ReferencePolicy.DYNAMIC,
policyOption = ReferencePolicyOption.GREEDY
)
- private volatile MailBuilder mailBuilder;
+ private volatile MessageIdProvider messageIdProvider;
@Reference(
cardinality = ReferenceCardinality.MANDATORY,
@@ -75,9 +82,34 @@ public class SimpleMailService implements MessageService {
)
private volatile ThreadPoolManager threadPoolManager;
- // the ThreadPool used for sending mails
+ @Reference(
+ cardinality = ReferenceCardinality.MANDATORY,
+ policy = ReferencePolicy.DYNAMIC,
+ policyOption = ReferencePolicyOption.GREEDY
+ )
+ private volatile CryptoService cryptoService;
+
+ @Reference(
+ cardinality = ReferenceCardinality.MULTIPLE,
+ policy = ReferencePolicy.DYNAMIC,
+ policyOption = ReferencePolicyOption.GREEDY
+ )
+ private volatile List<TransportListener> transportListeners;
+
private ThreadPool threadPool;
+ private SimpleMailServiceConfiguration configuration;
+
+ private Session session;
+
+ private static final String SMTPS_PROTOCOL = "smtps";
+
+ // https://javaee.github.io/javamail/docs/api/com/sun/mail/smtp/package-summary.html
+
+ private static final String MAIL_SMTPS_FROM = "mail.smtps.from";
+
+ private static final String MESSAGE_ID_HEADER = "Message-ID";
+
private final Logger logger = LoggerFactory.getLogger(SimpleMailService.class);
public SimpleMailService() {
@@ -85,46 +117,72 @@ public class SimpleMailService implements MessageService {
@Activate
private void activate(final SimpleMailServiceConfiguration configuration) {
- logger.debug("activate");
+ logger.debug("activating");
+ this.configuration = configuration;
configure(configuration);
}
@Modified
private void modified(final SimpleMailServiceConfiguration configuration) {
- logger.debug("modified");
+ logger.debug("modifying");
+ this.configuration = configuration;
configure(configuration);
}
@Deactivate
- protected void deactivate() {
- logger.info("deactivate");
+ private void deactivate() {
+ logger.debug("deactivating");
+ this.configuration = null;
threadPoolManager.release(threadPool);
threadPool = null;
+ session = null;
}
private void configure(final SimpleMailServiceConfiguration configuration) {
threadPoolManager.release(threadPool);
- threadPool = threadPoolManager.get(configuration.threadpoolName());
+ threadPool = threadPoolManager.get(configuration.threadpool_name());
+
+ final Properties properties = new Properties();
+ final String from = configuration.mail_smtps_from();
+ if (StringUtils.isNotBlank(from)) {
+ properties.setProperty(MAIL_SMTPS_FROM, from);
+ }
+
+ session = Session.getInstance(properties);
}
@Override
- public CompletableFuture<Result> send(@NotNull final String message, @NotNull final String recipient) {
- return send(message, recipient, Collections.emptyMap());
+ public @NotNull MessageBuilder getMessageBuilder() {
+ return new SimpleMessageBuilder(session);
}
@Override
- public CompletableFuture<Result> send(@NotNull final String message, @NotNull final String recipient, @NotNull final Map data) {
- return CompletableFuture.supplyAsync(() -> sendMail(message, recipient, data, mailBuilder), runnable -> threadPool.submit(runnable));
+ public @NotNull CompletableFuture<MimeMessage> sendMessage(@NotNull final MimeMessage message) {
+ return CompletableFuture.supplyAsync(() -> send(message), runnable -> threadPool.submit(runnable));
}
- private MailResult sendMail(final String message, final String recipient, final Map data, final MailBuilder mailBuilder) {
+ private @NotNull MimeMessage send(@NotNull final MimeMessage message) {
try {
- final Email email = mailBuilder.build(message, recipient, data);
- final String messageId = email.send();
- logger.info("mail '{}' sent", messageId);
- final byte[] bytes = MailUtil.toByteArray(email);
- return new MailResult(bytes);
- } catch (EmailException | MessagingException | IOException e) {
+ final ClassLoader tccl = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
+ final String password = cryptoService.decrypt(configuration.password());
+ try (final Transport transport = session.getTransport(SMTPS_PROTOCOL)) {
+ final List<TransportListener> listeners = this.transportListeners;
+ listeners.forEach(transport::addTransportListener);
+ transport.connect(configuration.mail_smtps_host(), configuration.mail_smtps_port(), configuration.username(), password);
+ message.saveChanges();
+ final MessageIdProvider messageIdProvider = this.messageIdProvider;
+ if (messageIdProvider != null) {
+ final String messageId = messageIdProvider.getMessageId(message);
+ message.setHeader(MESSAGE_ID_HEADER, String.format("<%s>", messageId));
+ }
+ logger.debug("sending message '{}'", message.getMessageID());
+ transport.sendMessage(message, message.getAllRecipients());
+ return message;
+ } finally {
+ Thread.currentThread().setContextClassLoader(tccl);
+ }
+ } catch (MessagingException e) {
throw new CompletionException(e);
}
}
diff --git a/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailServiceConfiguration.java b/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailServiceConfiguration.java
index 493041b..82a79fd 100644
--- a/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailServiceConfiguration.java
+++ b/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailServiceConfiguration.java
@@ -19,18 +19,80 @@
package org.apache.sling.commons.messaging.mail.internal;
import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
@ObjectClassDefinition(
name = "Apache Sling Commons Messaging Mail “Simple Mail Service”",
- description = "simple mail service for Sling Commons Messaging"
+ description = "Simple mail service sending MIME messages via SMTPS"
)
@interface SimpleMailServiceConfiguration {
@AttributeDefinition(
+ name = "Names",
+ description = "names of this service",
+ required = false
+ )
+ String[] names() default {"default"};
+
+ @AttributeDefinition(
name = "ThreadPool name",
description = "name of the ThreadPool to use for sending mails"
)
- String threadpoolName() default "default";
+ String threadpool_name() default "default";
+
+ @AttributeDefinition(
+ name = "SMTP from",
+ description = "from address"
+ )
+ String mail_smtps_from();
+
+ @AttributeDefinition(
+ name = "SMTP host",
+ description = "host of SMTP server"
+ )
+ String mail_smtps_host() default "localhost";
+
+ @AttributeDefinition(
+ name = "SMTP port",
+ description = "port of SMTP server"
+ )
+ int mail_smtps_port() default 465;
+
+ @AttributeDefinition(
+ name = "Username",
+ description = "username for SMTP server"
+ )
+ String username();
+
+ @AttributeDefinition(
+ name = "Password",
+ description = "password for SMTP server",
+ type = AttributeType.PASSWORD
+ )
+ String password();
+
+ @AttributeDefinition(
+ name = "Message ID Provider target",
+ description = "filter expression to target a Message ID Provider",
+ required = false
+ )
+ String messageIdProvider_target();
+
+ @AttributeDefinition(
+ name = "Crypto Service target",
+ description = "filter expression to target a Crypto Service",
+ required = false
+ )
+ String cryptoService_target();
+
+ @AttributeDefinition(
+ name = "Transport Listeners target",
+ description = "filter expression to target Transport Listeners",
+ required = false
+ )
+ String transportListeners_target();
+
+ String webconsole_configurationFactory_nameHint() default "{names} {username}@{mail_smtps_host}:{mail_smtps_port}";
}
diff --git a/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMessageBuilder.java b/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMessageBuilder.java
new file mode 100644
index 0000000..57a7b50
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMessageBuilder.java
@@ -0,0 +1,473 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.sling.commons.messaging.mail.internal;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.activation.DataHandler;
+import javax.activation.DataSource;
+import javax.mail.Address;
+import javax.mail.Header;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Part;
+import javax.mail.Session;
+import javax.mail.internet.AddressException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.InternetHeaders;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+import javax.mail.util.ByteArrayDataSource;
+
+import org.apache.sling.commons.messaging.mail.MessageBuilder;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class SimpleMessageBuilder implements MessageBuilder {
+
+ private final Session session;
+
+ private InternetHeaders headers = new InternetHeaders();
+
+ private InternetAddress from;
+
+ private List<InternetAddress> toRecipients = new LinkedList<>();
+
+ private List<InternetAddress> ccRecipients = new LinkedList<>();
+
+ private List<InternetAddress> bccRecipients = new LinkedList<>();
+
+ private List<InternetAddress> replyTos = new LinkedList<>();
+
+ private String subject;
+
+ private String text;
+
+ private String html;
+
+ private List<Attachment> attachments = new LinkedList<>();
+
+ private List<Inline> inlines = new LinkedList<>();
+
+ private static final String CONTENT_TYPE_TEXT_HTML = "text/html; charset=utf-8";
+
+ private static final String CONTENT_TYPE_TEXT_PLAIN = "text/plain; charset=utf-8";
+
+ private static final String CHARSET_UTF8 = "utf-8";
+
+ private static final String MULTIPART_SUBTYPE_MIXED = "mixed";
+
+ private static final String MULTIPART_SUBTYPE_ALTERNATIVE = "alternative";
+
+ private static final String MULTIPART_SUBTYPE_RELATED = "related";
+
+ SimpleMessageBuilder(@NotNull final Session session) {
+ this.session = session;
+ }
+
+ @Override
+ public @NotNull MessageBuilder header(@NotNull final String name, @Nullable final String value) {
+ headers.setHeader(name, value);
+ return this;
+ }
+
+ @Override
+ public @NotNull MessageBuilder headers(@NotNull final InternetHeaders headers) {
+ while (headers.getAllHeaders().hasMoreElements()) {
+ final Header header = headers.getAllHeaders().nextElement();
+ this.headers.setHeader(header.getName(), header.getValue());
+ }
+ return this;
+ }
+
+ @Override
+ public @NotNull MessageBuilder from(@NotNull final InternetAddress from) {
+ this.from = from;
+ return this;
+ }
+
+ @Override
+ public @NotNull MessageBuilder from(@NotNull final String address) throws AddressException {
+ final InternetAddress from = new InternetAddress(address);
+ return from(from);
+ }
+
+ @Override
+ public @NotNull MessageBuilder from(@NotNull final String address, @NotNull final String name) throws AddressException {
+ final InternetAddress from = new InternetAddress(address);
+ try {
+ from.setPersonal(name, StandardCharsets.UTF_8.name());
+ } catch (UnsupportedEncodingException e) {
+ //
+ }
+ return from(from);
+ }
+
+ @Override
+ public @NotNull MessageBuilder to(@NotNull final InternetAddress to) {
+ toRecipients.add(to);
+ return this;
+ }
+
+ @Override
+ public @NotNull MessageBuilder to(@NotNull final String address) throws AddressException {
+ final InternetAddress to = new InternetAddress(address);
+ return to(to);
+ }
+
+ @Override
+ public @NotNull MessageBuilder to(@NotNull final String address, @NotNull final String name) throws AddressException {
+ final InternetAddress to = new InternetAddress(address);
+ try {
+ to.setPersonal(name, StandardCharsets.UTF_8.name());
+ } catch (UnsupportedEncodingException e) {
+ //
+ }
+ return to(to);
+ }
+
+ @Override
+ public @NotNull MessageBuilder cc(@NotNull final InternetAddress cc) {
+ ccRecipients.add(cc);
+ return this;
+ }
+
+ @Override
+ public @NotNull MessageBuilder cc(@NotNull final String address) throws AddressException {
+ final InternetAddress cc = new InternetAddress(address);
+ return cc(cc);
+ }
+
+ @Override
+ public @NotNull MessageBuilder cc(@NotNull final String address, @NotNull final String name) throws AddressException {
+ final InternetAddress cc = new InternetAddress(address);
+ try {
+ cc.setPersonal(name, StandardCharsets.UTF_8.name());
+ } catch (UnsupportedEncodingException e) {
+ //
+ }
+ return cc(cc);
+ }
+
+ @Override
+ public @NotNull MessageBuilder bcc(@NotNull final InternetAddress bcc) {
+ bccRecipients.add(bcc);
+ return this;
+ }
+
+ @Override
+ public @NotNull MessageBuilder bcc(@NotNull final String address) throws AddressException {
+ final InternetAddress bcc = new InternetAddress(address);
+ return bcc(bcc);
+ }
+
+ public @NotNull MessageBuilder bcc(@NotNull final String address, final String name) throws AddressException {
+ final InternetAddress bcc = new InternetAddress(address);
+ try {
+ bcc.setPersonal(name, StandardCharsets.UTF_8.name());
+ } catch (UnsupportedEncodingException e) {
+ //
+ }
+ return bcc(bcc);
+ }
+
+ @Override
+ public @NotNull MessageBuilder replyTo(@NotNull final InternetAddress replyTo) {
+ replyTos.add(replyTo);
+ return this;
+ }
+
+ @Override
+ public @NotNull MessageBuilder replyTo(@NotNull final String address) throws AddressException {
+ final InternetAddress replyTo = new InternetAddress(address);
+ return replyTo(replyTo);
+ }
+
+ @Override
+ public @NotNull MessageBuilder replyTo(@NotNull final String address, @NotNull final String name) throws AddressException {
+ final InternetAddress replyTo = new InternetAddress(address);
+ try {
+ replyTo.setPersonal(name, StandardCharsets.UTF_8.name());
+ } catch (UnsupportedEncodingException e) {
+ //
+ }
+ return replyTo(replyTo);
+ }
+
+ @Override
+ public @NotNull MessageBuilder subject(@NotNull final String subject) {
+ this.subject = subject;
+ return this;
+ }
+
+ @Override
+ public @NotNull MessageBuilder text(@NotNull final String text) {
+ this.text = text;
+ return this;
+ }
+
+ @Override
+ public @NotNull MessageBuilder html(@NotNull final String html) {
+ this.html = html;
+ return this;
+ }
+
+ @Override
+ public @NotNull MessageBuilder attachment(@NotNull final byte[] content, @NotNull final String type, @NotNull final String filename) {
+ return attachment(content, type, filename, null);
+ }
+
+ @Override
+ public @NotNull MessageBuilder attachment(@NotNull final byte[] content, @NotNull final String type, @NotNull final String filename, @Nullable Header[] headers) {
+ final Attachment attachment = new Attachment(content, type, filename, null);
+ this.attachments.add(attachment);
+ return this;
+ }
+
+ @Override
+ public @NotNull MessageBuilder inline(@NotNull final byte[] content, @NotNull final String type, @NotNull final String cid) {
+ return inline(content, type, cid, null);
+ }
+
+ @Override
+ public @NotNull MessageBuilder inline(@NotNull final byte[] content, @NotNull final String type, @NotNull final String cid, @Nullable Header[] headers) {
+ final Inline inline = new Inline(content, type, cid, headers);
+ this.inlines.add(inline);
+ return this;
+ }
+
+ private InternetHeaders headers() {
+ return headers;
+ }
+
+ private InternetAddress from() {
+ return from;
+ }
+
+ private List<InternetAddress> to() {
+ return toRecipients;
+ }
+
+ private List<InternetAddress> cc() {
+ return ccRecipients;
+ }
+
+ private List<InternetAddress> bcc() {
+ return bccRecipients;
+ }
+
+ private List<InternetAddress> replyTo() {
+ return replyTos;
+ }
+
+ private String subject() {
+ return subject;
+ }
+
+ private String text() {
+ return text;
+ }
+
+ private String html() {
+ return html;
+ }
+
+ private List<Attachment> attachments() {
+ return attachments;
+ }
+
+ private List<Inline> inlines() {
+ return inlines;
+ }
+
+ private boolean hasText() {
+ return text() != null;
+ }
+
+ private boolean hasHtml() {
+ return html() != null;
+ }
+
+ private boolean hasAttachments() {
+ return !attachments().isEmpty();
+ }
+
+ private boolean hasInlines() {
+ return !inlines().isEmpty();
+ }
+
+ public @NotNull MimeMessage build() throws MessagingException {
+ final MimeMessage message = new MimeMessage(session);
+
+ while (headers().getAllHeaders().hasMoreElements()) {
+ final Header header = headers.getAllHeaders().nextElement();
+ message.setHeader(header.getName(), header.getValue());
+ }
+
+ message.setFrom(from());
+ message.setRecipients(Message.RecipientType.TO, to().toArray(new Address[0]));
+ message.setRecipients(Message.RecipientType.CC, cc().toArray(new Address[0]));
+ message.setRecipients(Message.RecipientType.BCC, bcc().toArray(new Address[0]));
+ message.setReplyTo(replyTos.toArray(new Address[0]));
+ message.setSubject(subject(), StandardCharsets.UTF_8.name());
+
+ if (hasHtml() || hasAttachments() || hasInlines()) {
+ final MimeMultipart content = new MimeMultipart(MULTIPART_SUBTYPE_MIXED);
+
+ if (hasText() && hasHtml()) { // text and html
+ final MimeMultipart alternative = new MimeMultipart(MULTIPART_SUBTYPE_ALTERNATIVE);
+ handleHtmlAndInlines(alternative, html(), inlines());
+ addText(alternative, text());
+ final MimeBodyPart part = new MimeBodyPart();
+ part.setContent(alternative);
+ content.addBodyPart(part);
+ } else if (hasHtml()) { // html only
+ handleHtmlAndInlines(content, html(), inlines());
+ } else { // text only
+ addText(content, text());
+ }
+
+ addAttachments(content, attachments);
+
+ message.setContent(content);
+ } else {
+ message.setText(text(), CHARSET_UTF8);
+ }
+ return message;
+ }
+
+ private static void handleHtmlAndInlines(final MimeMultipart parent, final String html, final List<Inline> inlines) throws MessagingException {
+ if (!inlines.isEmpty()) { // html and inlines
+ final MimeMultipart related = new MimeMultipart(MULTIPART_SUBTYPE_RELATED);
+ addHtml(related, html);
+ addInlines(related, inlines);
+ final MimeBodyPart part = new MimeBodyPart();
+ part.setContent(related);
+ parent.addBodyPart(part);
+ } else { // html
+ addHtml(parent, html);
+ }
+ }
+
+ private static void addText(final MimeMultipart parent, final String text) throws MessagingException {
+ final MimeBodyPart part = new MimeBodyPart();
+ part.setContent(text, CONTENT_TYPE_TEXT_PLAIN);
+ parent.addBodyPart(part);
+ }
+
+ private static void addHtml(final MimeMultipart parent, final String html) throws MessagingException {
+ final MimeBodyPart part = new MimeBodyPart();
+ part.setContent(html, CONTENT_TYPE_TEXT_HTML);
+ parent.addBodyPart(part);
+ }
+
+ private static void addAttachments(final MimeMultipart parent, final List<Attachment> attachments) throws MessagingException {
+ for (final Attachment attachment : attachments) {
+ try (final ByteArrayInputStream inputStream = new ByteArrayInputStream(attachment.content)) {
+ final MimeBodyPart part = new MimeBodyPart();
+ part.setDisposition(Part.ATTACHMENT);
+ part.setFileName(attachment.filename);
+ setDataHandler(part, inputStream, attachment.type);
+ if (attachment.headers != null) {
+ setHeaders(part, attachment.headers);
+ }
+ parent.addBodyPart(part);
+ } catch (Exception e) {
+ final String message = String.format("Adding attachment failed: %s", attachment.filename);
+ throw new MessagingException(message, e);
+ }
+ }
+ }
+
+ private static void addInlines(final MimeMultipart parent, final List<Inline> inlines) throws MessagingException {
+ for (final Inline inline : inlines) {
+ try (final ByteArrayInputStream inputStream = new ByteArrayInputStream(inline.content)) {
+ final MimeBodyPart part = new MimeBodyPart();
+ part.setDisposition(Part.INLINE);
+ part.setContentID(String.format("<%s>", inline.cid));
+ setDataHandler(part, inputStream, inline.type);
+ if (inline.headers != null) {
+ setHeaders(part, inline.headers);
+ }
+ parent.addBodyPart(part);
+ } catch (Exception e) {
+ final String message = String.format("Adding inline object failed: %s", inline.cid);
+ throw new MessagingException(message, e);
+ }
+ }
+ }
+
+ private static void setDataHandler(final MimeBodyPart part, final InputStream inputStream, final String type) throws MessagingException, IOException {
+ final DataSource source = new ByteArrayDataSource(inputStream, type);
+ final DataHandler handler = new DataHandler(source);
+ part.setDataHandler(handler);
+ }
+
+ private static void setHeaders(final MimeBodyPart part, final Header[] headers) throws MessagingException {
+ for (final Header header : headers) {
+ part.setHeader(header.getName(), header.getValue());
+ }
+ }
+
+ private static class Attachment {
+
+ final byte[] content;
+
+ final String type;
+
+ final String filename;
+
+ final Header[] headers;
+
+ Attachment(@NotNull final byte[] content, @NotNull final String type, @NotNull final String filename, @Nullable final Header[] headers) {
+ this.content = content;
+ this.type = type;
+ this.filename = filename;
+ this.headers = headers;
+ }
+
+ }
+
+ private static class Inline {
+
+ final byte[] content;
+
+ final String type;
+
+ final String cid;
+
+ final Header[] headers;
+
+ Inline(@NotNull final byte[] content, @NotNull final String type, @NotNull final String cid, @Nullable final Header[] headers) {
+ this.content = content;
+ this.type = type;
+ this.cid = cid;
+ this.headers = headers;
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMessageIdProvider.java b/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMessageIdProvider.java
new file mode 100644
index 0000000..e8c231c
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMessageIdProvider.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.sling.commons.messaging.mail.internal;
+
+import java.util.UUID;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.sling.commons.messaging.mail.MessageIdProvider;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.metatype.annotations.Designate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(
+ property = {
+ Constants.SERVICE_DESCRIPTION + "=Apache Sling Commons Messaging Mail – Simple Message ID Provider",
+ Constants.SERVICE_VENDOR + "=The Apache Software Foundation"
+ }
+)
+@Designate(
+ ocd = SimpleMessageIdProviderConfiguration.class,
+ factory = true
+)
+public class SimpleMessageIdProvider implements MessageIdProvider {
+
+ private SimpleMessageIdProviderConfiguration configuration;
+
+ private final Logger logger = LoggerFactory.getLogger(SimpleMessageIdProvider.class);
+
+ @Activate
+ private void activate(final SimpleMessageIdProviderConfiguration configuration) {
+ logger.debug("activating");
+ this.configuration = configuration;
+ }
+
+ @Modified
+ private void modified(final SimpleMessageIdProviderConfiguration configuration) {
+ logger.debug("modifying");
+ this.configuration = configuration;
+ }
+
+ @Deactivate
+ private void deactivate() {
+ logger.debug("deactivating");
+ this.configuration = null;
+ }
+
+ @Override
+ public @NotNull String getMessageId(@NotNull MimeMessage message) throws MessagingException {
+ return String.format("%s.%s@%s", UUID.randomUUID().toString(), System.currentTimeMillis(), configuration.host());
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailServiceConfiguration.java b/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMessageIdProviderConfiguration.java
similarity index 63%
copy from src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailServiceConfiguration.java
copy to src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMessageIdProviderConfiguration.java
index 493041b..d098909 100644
--- a/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailServiceConfiguration.java
+++ b/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMessageIdProviderConfiguration.java
@@ -22,15 +22,24 @@ import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
@ObjectClassDefinition(
- name = "Apache Sling Commons Messaging Mail “Simple Mail Service”",
- description = "simple mail service for Sling Commons Messaging"
+ name = "Apache Sling Commons Messaging Mail “Simple Message ID Provider”",
+ description = "Service to provide a Message ID based on random UUID, timestamp in ms and custom host"
)
-@interface SimpleMailServiceConfiguration {
+@interface SimpleMessageIdProviderConfiguration {
@AttributeDefinition(
- name = "ThreadPool name",
- description = "name of the ThreadPool to use for sending mails"
+ name = "Names",
+ description = "names of this service",
+ required = false
)
- String threadpoolName() default "default";
+ String[] names() default {"default"};
+
+ @AttributeDefinition(
+ name = "Host",
+ description = "Host to use in Message ID"
+ )
+ String host() default "localhost";
+
+ String webconsole_configurationFactory_nameHint() default "{names} {host}";
}
diff --git a/src/test/java/org/apache/sling/commons/messaging/mail/it/tests/MailTestSupport.java b/src/test/java/org/apache/sling/commons/messaging/mail/it/tests/MailTestSupport.java
new file mode 100644
index 0000000..6938dee
--- /dev/null
+++ b/src/test/java/org/apache/sling/commons/messaging/mail/it/tests/MailTestSupport.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.sling.commons.messaging.mail.it.tests;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.sling.testing.paxexam.SlingOptions;
+import org.apache.sling.testing.paxexam.TestSupport;
+import org.ops4j.pax.exam.options.MavenArtifactProvisionOption;
+import org.ops4j.pax.exam.options.ModifiableCompositeOption;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.thymeleaf.ITemplateEngine;
+import org.thymeleaf.TemplateEngine;
+import org.thymeleaf.TemplateSpec;
+import org.thymeleaf.context.Context;
+import org.thymeleaf.context.IContext;
+import org.thymeleaf.templatemode.TemplateMode;
+
+import static org.apache.sling.testing.paxexam.SlingOptions.backing;
+import static org.apache.sling.testing.paxexam.SlingOptions.scr;
+import static org.apache.sling.testing.paxexam.SlingOptions.slingCommonsThreads;
+import static org.ops4j.pax.exam.CoreOptions.bootClasspathLibrary;
+import static org.ops4j.pax.exam.CoreOptions.composite;
+import static org.ops4j.pax.exam.CoreOptions.junitBundles;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.wrappedBundle;
+
+public abstract class MailTestSupport extends TestSupport {
+
+ @Inject
+ protected ConfigurationAdmin configurationAdmin;
+
+ private ITemplateEngine templateEngine = new TemplateEngine();
+
+ protected ModifiableCompositeOption baseConfiguration() {
+ return composite(
+ super.baseConfiguration(),
+ // Sling Commons Messaging Mail
+ testBundle("bundle.filename"),
+ mavenBundle().groupId("org.apache.sling").artifactId("org.apache.sling.commons.messaging").versionAsInProject(),
+ mavenBundle().groupId("jakarta.mail").artifactId("jakarta.mail-api").versionAsInProject(),
+ mavenBundle().groupId("com.sun.mail").artifactId("jakarta.mail").versionAsInProject(),
+ mavenBundle().groupId("org.apache.commons").artifactId("commons-lang3").versionAsInProject(),
+ scr(),
+ slingCommonsCrypto(),
+ slingCommonsThreads(),
+ backing(),
+ // testing
+ junitBundles(),
+ wrappedBundle(mavenBundle().groupId("com.google.truth").artifactId("truth").versionAsInProject()),
+ mavenBundle().groupId("com.google.guava").artifactId("guava").versionAsInProject(),
+ mavenBundle().groupId("com.google.guava").artifactId("failureaccess").versionAsInProject(),
+ mavenBundle().groupId("com.googlecode.java-diff-utils").artifactId("diffutils").versionAsInProject(),
+ mavenBundle().groupId("commons-io").artifactId("commons-io").versionAsInProject(),
+ mavenBundle().groupId("org.apache.commons").artifactId("commons-email").versionAsInProject(),
+ greenmail(),
+ thymeleaf()
+ );
+ }
+
+ private static ModifiableCompositeOption greenmail() {
+ final MavenArtifactProvisionOption greenmail = mavenBundle().groupId("com.icegreen").artifactId("greenmail").versionAsInProject();
+ final MavenArtifactProvisionOption slf4j_api = mavenBundle().groupId("org.slf4j").artifactId("slf4j-api").versionAsInProject();
+ final MavenArtifactProvisionOption slf4j_simple = mavenBundle().groupId("org.slf4j").artifactId("slf4j-simple").versionAsInProject();
+ return composite(
+ greenmail,
+ // add GreenMail to boot classpath to allow setting ssl.SocketFactory.provider to GreenMail's DummySSLSocketFactory
+ bootClasspathLibrary(greenmail).afterFramework(),
+ bootClasspathLibrary(slf4j_api).afterFramework(), // GreenMail dependency
+ bootClasspathLibrary(slf4j_simple).afterFramework() // GreenMail dependency
+ );
+ }
+
+ private static ModifiableCompositeOption thymeleaf() {
+ return composite(
+ mavenBundle().groupId("org.apache.servicemix.bundles").artifactId("org.apache.servicemix.bundles.thymeleaf").version(SlingOptions.versionResolver),
+ mavenBundle().groupId("org.attoparser").artifactId("attoparser").version(SlingOptions.versionResolver),
+ mavenBundle().groupId("org.unbescape").artifactId("unbescape").version(SlingOptions.versionResolver),
+ mavenBundle().groupId("org.apache.servicemix.bundles").artifactId("org.apache.servicemix.bundles.ognl").version(SlingOptions.versionResolver),
+ mavenBundle().groupId("org.javassist").artifactId("javassist").version(SlingOptions.versionResolver)
+ );
+ }
+
+ private static ModifiableCompositeOption slingCommonsCrypto() {
+ return composite(
+ mavenBundle().groupId("org.apache.sling").artifactId("org.apache.sling.commons.crypto").versionAsInProject(),
+ mavenBundle().groupId("org.apache.commons").artifactId("commons-lang3").versionAsInProject(),
+ mavenBundle().groupId("org.apache.servicemix.bundles").artifactId("org.apache.servicemix.bundles.jasypt").versionAsInProject()
+ );
+ }
+
+ // helpers for attachments, inline objects and templates
+
+ String getResourceAsString(final String path) throws IOException {
+ try (final InputStream inputStream = getClass().getResourceAsStream(path)) {
+ return IOUtils.toString(inputStream, StandardCharsets.UTF_8);
+ }
+ }
+
+ byte[] getResourceAsByteArray(final String path) throws IOException {
+ try (final InputStream inputStream = getClass().getResourceAsStream(path)) {
+ return IOUtils.toByteArray(inputStream);
+ }
+ }
+
+ String renderHtmlTemplate(final String path, final Map<String, Object> variables) throws IOException {
+ return renderTemplate(path, variables, TemplateMode.HTML);
+ }
+
+ String renderTextTemplate(final String path, final Map<String, Object> variables) throws IOException {
+ return renderTemplate(path, variables, TemplateMode.TEXT);
+ }
+
+ String renderTemplate(final String path, final Map<String, Object> variables, final TemplateMode templateMode) throws IOException {
+ final String template = getResourceAsString(path);
+ final IContext context = new Context(Locale.ENGLISH, variables);
+ final TemplateSpec templateSpec = new TemplateSpec(template, templateMode);
+ return templateEngine.process(templateSpec, context);
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/commons/messaging/mail/it/tests/SimpleMailServiceIT.java b/src/test/java/org/apache/sling/commons/messaging/mail/it/tests/SimpleMailServiceIT.java
new file mode 100644
index 0000000..2557f28
--- /dev/null
+++ b/src/test/java/org/apache/sling/commons/messaging/mail/it/tests/SimpleMailServiceIT.java
@@ -0,0 +1,414 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.sling.commons.messaging.mail.it.tests;
+
+import java.io.UnsupportedEncodingException;
+import java.security.Security;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+import javax.activation.DataSource;
+import javax.inject.Inject;
+import javax.mail.Message;
+import javax.mail.Session;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+
+import com.icegreen.greenmail.util.DummySSLSocketFactory;
+import com.icegreen.greenmail.util.GreenMail;
+import com.icegreen.greenmail.util.ServerSetup;
+import org.apache.commons.mail.util.MimeMessageParser;
+import org.apache.sling.commons.messaging.MessageService;
+import org.apache.sling.commons.messaging.mail.MailService;
+import org.apache.sling.commons.messaging.mail.MessageBuilder;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
+import org.ops4j.pax.exam.util.Filter;
+import org.ops4j.pax.exam.util.PathUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.propagateSystemProperties;
+import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.factoryConfiguration;
+
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerClass.class)
+public class SimpleMailServiceIT extends MailTestSupport {
+
+ private static final boolean local = !Boolean.getBoolean("sling.test.mail.smtps.server.external");
+
+ private static InternetAddress from;
+
+ private static InternetAddress to;
+
+ private static InternetAddress cc;
+
+ private static InternetAddress bcc;
+
+ private static InternetAddress replyTo;
+
+ static {
+ final String from_address = local ? "from@example.org" : System.getProperty("sling.test.mail.from.address");
+ final String from_name = local ? "From Name" : System.getProperty("sling.test.mail.from.name");
+ final String to_address = local ? "to@example.org" : System.getProperty("sling.test.mail.to.address");
+ final String to_name = local ? "To Name" : System.getProperty("sling.test.mail.to.name");
+ final String replyTo_address = local ? "replyto@example.org" : System.getProperty("sling.test.mail.replyTo.address");
+ final String replyTo_name = local ? "ReplyTo Name" : System.getProperty("sling.test.mail.replyTo.name");
+ try {
+ from = new InternetAddress(from_address, from_name);
+ to = new InternetAddress(to_address, to_name);
+ replyTo = new InternetAddress(replyTo_address, replyTo_name);
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private GreenMail greenMail;
+
+ @Inject
+ protected MessageService<MimeMessage> messageService;
+
+ @Inject
+ @Filter(value = "(protocol=SMTPS)")
+ protected MailService mailService;
+
+ @Rule
+ public final ExpectedException exception = ExpectedException.none();
+
+ private final Logger logger = LoggerFactory.getLogger(SimpleMailServiceIT.class);
+
+ @Configuration
+ public Option[] configuration() {
+ final int port = findFreePort();
+ final String path = String.format("%s/src/test/resources/password", PathUtils.getBaseDir());
+ return options(
+ baseConfiguration(),
+ propagateSystemProperties(
+ "sling.test.mail.smtps.server.external",
+ "sling.test.mail.smtps.from",
+ "sling.test.mail.smtps.host",
+ "sling.test.mail.smtps.port",
+ "sling.test.mail.smtps.username",
+ "sling.test.mail.smtps.password",
+ "sling.test.mail.from.address",
+ "sling.test.mail.from.name",
+ "sling.test.mail.to.address",
+ "sling.test.mail.to.name",
+ "sling.test.mail.replyTo.address",
+ "sling.test.mail.replyTo.name"
+ ),
+ factoryConfiguration("org.apache.sling.commons.messaging.mail.internal.SimpleMessageIdProvider")
+ .put("host", "localhost")
+ .asOption(),
+ factoryConfiguration("org.apache.sling.commons.messaging.mail.internal.SimpleMailService")
+ .put("mail.smtps.from", local ? "envelope-from@example.org" : System.getProperty("sling.test.mail.smtps.from"))
+ .put("mail.smtps.host", local ? "localhost" : System.getProperty("sling.test.mail.smtps.host"))
+ .put("mail.smtps.port", local ? port : Integer.getInteger("sling.test.mail.smtps.port"))
+ .put("username", local ? "username" : System.getProperty("sling.test.mail.smtps.username"))
+ .put("password", local ? "OEKPFL5cVJRqVjh4QaDZhvBiqv8wgWBMJ8PGbYHTqev046oV6888mna9w1mIGCXK" : System.getProperty("sling.test.mail.smtps.password"))
+ .asOption(),
+ // Commons Crypto
+ factoryConfiguration("org.apache.sling.commons.crypto.jasypt.internal.JasyptStandardPBEStringCryptoService")
+ .put("algorithm", "PBEWITHHMACSHA512ANDAES_256")
+ .asOption(),
+ factoryConfiguration("org.apache.sling.commons.crypto.jasypt.internal.JasyptRandomIvGeneratorRegistrar")
+ .put("algorithm", "SHA1PRNG")
+ .asOption(),
+ factoryConfiguration("org.apache.sling.commons.crypto.internal.FilePasswordProvider")
+ .put("path", path)
+ .asOption()
+ );
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ logger.info("local server : {}", local);
+ if (local && Objects.isNull(greenMail)) {
+ // set up GreenMail server
+ Security.setProperty("ssl.SocketFactory.provider", DummySSLSocketFactory.class.getName());
+ final org.osgi.service.cm.Configuration[] configurations = configurationAdmin.listConfigurations("(service.factoryPid=org.apache.sling.commons.messaging.mail.internal.SimpleMailService)");
+ final org.osgi.service.cm.Configuration configuration = configurations[0];
+ final int port = (int) configuration.getProperties().get("mail.smtps.port");
+ final ServerSetup serverSetup = new ServerSetup(port, "127.0.0.1", "smtps");
+ greenMail = new GreenMail(serverSetup);
+ greenMail.setUser("username", "password");
+ greenMail.start();
+ }
+ }
+
+ @After
+ public void tearDown() {
+ if (local) {
+ greenMail.stop();
+ greenMail = null;
+ }
+ }
+
+ private MessageBuilder initializeMessageBuilder() {
+ return mailService.getMessageBuilder()
+ .from(from)
+ .to(to)
+ .replyTo(replyTo);
+ }
+
+ @Test
+ public void testMessageService() throws ExecutionException, InterruptedException {
+ exception.expect(ExecutionException.class);
+ assertThat(messageService).isNotNull();
+ final Properties properties = new Properties();
+ final Session session = Session.getDefaultInstance(properties);
+ final MimeMessage message = new MimeMessage(session);
+ final CompletableFuture<MimeMessage> future = messageService.sendMessage(message);
+ future.get();
+ }
+
+ @Test
+ public void testMailService() {
+ assertThat(mailService).isNotNull();
+ }
+
+ @Test
+ public void sendText() throws Exception {
+ final Map<String, Object> variables = Collections.singletonMap("date", new Date());
+ final String subject = "Sling Commons Mail: Text [æåëęïįœøüū] \uD83D\uDCE7";
+ final String text = renderTextTemplate("/template.txt", variables);
+ final MimeMessage message = initializeMessageBuilder()
+ .subject(subject)
+ .text(text)
+ .build();
+
+ final CompletableFuture<MimeMessage> future = mailService.sendMessage(message);
+ future.get();
+
+ if (local) {
+ greenMail.waitForIncomingEmail(1);
+ greenMail.getReceivedMessagesForDomain(to.getAddress());
+ final MimeMessage[] messages = greenMail.getReceivedMessages();
+ final MimeMessage received = messages[0];
+ final MimeMessageParser parser = new MimeMessageParser(message).parse();
+
+ assertThat(received.getMessageID()).endsWith("@localhost>");
+ assertThat(received.getSubject()).isEqualTo(subject);
+ assertThat(received.getFrom()[0]).isEqualTo(from);
+ assertThat(received.getRecipients(Message.RecipientType.TO)[0]).isEqualTo(to);
+ assertThat(received.getReplyTo()[0]).isEqualTo(replyTo);
+
+ assertThat(parser.getPlainContent()).isEqualTo(text);
+
+ assertThat(parser.getAttachmentList()).isEmpty();
+ assertThat(parser.getContentIds()).isEmpty();
+ }
+ }
+
+ @Test
+ public void sendTextAndAttachment() throws Exception {
+ final Map<String, Object> variables = Collections.singletonMap("date", new Date());
+ final String subject = "Sling Commons Mail: Text and Attachment [æåëęïįœøüū] \uD83D\uDCE7";
+ final String text = renderTextTemplate("/template.txt", variables);
+ final byte[] support = getResourceAsByteArray("/SupportApache-small.png");
+ final MimeMessage message = initializeMessageBuilder()
+ .subject(subject)
+ .text(text)
+ .attachment(support, "image/png", "SupportApache-small.png")
+ .build();
+
+ final CompletableFuture<MimeMessage> future = mailService.sendMessage(message);
+ future.get();
+
+ if (local) {
+ greenMail.waitForIncomingEmail(1);
+ final MimeMessage[] messages = greenMail.getReceivedMessages();
+ final MimeMessage received = messages[0];
+ final MimeMessageParser parser = new MimeMessageParser(message).parse();
+
+ assertThat(received.getMessageID()).endsWith("@localhost>");
+ assertThat(received.getSubject()).isEqualTo(subject);
+ assertThat(received.getFrom()[0]).isEqualTo(from);
+ assertThat(received.getRecipients(Message.RecipientType.TO)[0]).isEqualTo(to);
+ assertThat(received.getReplyTo()[0]).isEqualTo(replyTo);
+
+ assertThat(parser.getPlainContent()).isEqualTo(text);
+
+ assertThat(parser.getAttachmentList().get(0).getName()).isEqualTo("SupportApache-small.png");
+ assertThat(parser.getContentIds()).isEmpty();
+ }
+ }
+
+ @Test
+ public void sendHtml() throws Exception {
+ final Map<String, Object> variables = Collections.singletonMap("date", new Date());
+ final String subject = "Sling Commons Mail: HTML [æåëęïįœøüū] \uD83D\uDCE7";
+ final String html = renderHtmlTemplate("/template.html", variables);
+ final MimeMessage message = initializeMessageBuilder()
+ .subject(subject)
+ .html(html)
+ .build();
+
+ final CompletableFuture<MimeMessage> future = mailService.sendMessage(message);
+ future.get();
+
+ if (local) {
+ greenMail.waitForIncomingEmail(1);
+ final MimeMessage[] messages = greenMail.getReceivedMessages();
+ final MimeMessage received = messages[0];
+ final MimeMessageParser parser = new MimeMessageParser(message).parse();
+
+ assertThat(received.getMessageID()).endsWith("@localhost>");
+ assertThat(received.getSubject()).isEqualTo(subject);
+ assertThat(received.getFrom()[0]).isEqualTo(from);
+ assertThat(received.getRecipients(Message.RecipientType.TO)[0]).isEqualTo(to);
+ assertThat(received.getReplyTo()[0]).isEqualTo(replyTo);
+
+ assertThat(parser.getHtmlContent()).isEqualTo(html);
+
+ assertThat(parser.getAttachmentList()).isEmpty();
+ assertThat(parser.getContentIds()).isEmpty();
+ }
+ }
+
+ @Test
+ public void sendHtmlAndAttachment() throws Exception {
+ final Map<String, Object> variables = Collections.singletonMap("date", new Date());
+ final String subject = "Sling Commons Mail: HTML and Attachment [æåëęïįœøüū] \uD83D\uDCE7";
+ final String html = renderHtmlTemplate("/template.html", variables);
+ final byte[] support = getResourceAsByteArray("/SupportApache-small.png");
+ final MimeMessage message = initializeMessageBuilder()
+ .subject(subject)
+ .html(html)
+ .attachment(support, "image/png", "SupportApache-small.png")
+ .build();
+
+ final CompletableFuture<MimeMessage> future = mailService.sendMessage(message);
+ future.get();
+
+ if (local) {
+ greenMail.waitForIncomingEmail(1);
+ final MimeMessage[] messages = greenMail.getReceivedMessages();
+ final MimeMessage received = messages[0];
+ final MimeMessageParser parser = new MimeMessageParser(message).parse();
+
+ assertThat(received.getMessageID()).endsWith("@localhost>");
+ assertThat(received.getSubject()).isEqualTo(subject);
+ assertThat(received.getFrom()[0]).isEqualTo(from);
+ assertThat(received.getRecipients(Message.RecipientType.TO)[0]).isEqualTo(to);
+ assertThat(received.getReplyTo()[0]).isEqualTo(replyTo);
+
+ assertThat(parser.getHtmlContent()).isEqualTo(html);
+
+ assertThat(parser.getAttachmentList().get(0).getName()).isEqualTo("SupportApache-small.png");
+ assertThat(parser.getContentIds()).isEmpty();
+ }
+ }
+
+ @Test
+ public void sendHtmlWithInlineImageAndAttachment() throws Exception {
+ final Map<String, Object> variables = Collections.singletonMap("date", new Date());
+ final String subject = "Sling Commons Mail: HTML with Inline Images and Attachment [æåëęïįœøüū] \uD83D\uDCE7";
+ final String html = renderHtmlTemplate("/template-inlines.html", variables);
+ final byte[] sling = getResourceAsByteArray("/sling.png");
+ final byte[] support = getResourceAsByteArray("/SupportApache-small.png");
+ final MimeMessage message = initializeMessageBuilder()
+ .subject(subject)
+ .html(html)
+ .attachment(support, "image/png", "SupportApache-small.png")
+ .inline(sling, "image/png", "sling")
+ .build();
+
+ final CompletableFuture<MimeMessage> future = mailService.sendMessage(message);
+ future.get();
+
+ if (local) {
+ greenMail.waitForIncomingEmail(1);
+ final MimeMessage[] messages = greenMail.getReceivedMessages();
+ final MimeMessage received = messages[0];
+ final MimeMessageParser parser = new MimeMessageParser(message).parse();
+
+ assertThat(received.getMessageID()).endsWith("@localhost>");
+ assertThat(received.getSubject()).isEqualTo(subject);
+ assertThat(received.getFrom()[0]).isEqualTo(from);
+ assertThat(received.getRecipients(Message.RecipientType.TO)[0]).isEqualTo(to);
+ assertThat(received.getReplyTo()[0]).isEqualTo(replyTo);
+
+ assertThat(parser.getHtmlContent()).isEqualTo(html);
+
+ assertThat(parser.getContentIds()).contains("sling");
+ }
+ }
+
+ @Test
+ public void sendHtmlWithInlineImageAndTextAndAttachment() throws Exception {
+ final Map<String, Object> variables = Collections.singletonMap("date", new Date());
+ final String subject = "Sling Commons Mail: HTML with Inline Images and Text and Attachment [æåëęïįœøüū] \uD83D\uDCE7";
+ final String text = renderTextTemplate("/template.txt", variables);
+ final String html = renderHtmlTemplate("/template-inlines.html", variables);
+ final byte[] sling = getResourceAsByteArray("/sling.png");
+ final byte[] support = getResourceAsByteArray("/SupportApache-small.png");
+ final MimeMessage message = initializeMessageBuilder()
+ .subject(subject)
+ .text(text)
+ .html(html)
+ .attachment(support, "image/png", "SupportApache-small.png")
+ .inline(sling, "image/png", "sling")
+ .build();
+
+ final CompletableFuture<MimeMessage> future = mailService.sendMessage(message);
+ future.get();
+
+ if (local) {
+ greenMail.waitForIncomingEmail(1);
+ final MimeMessage[] messages = greenMail.getReceivedMessages();
+ final MimeMessage received = messages[0];
+ final MimeMessageParser parser = new MimeMessageParser(message).parse();
+
+ assertThat(received.getMessageID()).endsWith("@localhost>");
+ assertThat(received.getSubject()).isEqualTo(subject);
+ assertThat(received.getFrom()[0]).isEqualTo(from);
+ assertThat(received.getRecipients(Message.RecipientType.TO)[0]).isEqualTo(to);
+ assertThat(received.getReplyTo()[0]).isEqualTo(replyTo);
+
+ assertThat(parser.getPlainContent()).isEqualTo(text);
+ assertThat(parser.getHtmlContent()).isEqualTo(html);
+
+ final List<DataSource> sources = parser.getAttachmentList();
+ for (DataSource source : sources) {
+ logger.info("data source: {}, {}", source.getName(), source.getContentType());
+ }
+
+ assertThat(parser.getContentIds()).contains("sling");
+ }
+ }
+
+}
diff --git a/src/test/resources/SupportApache-small.png b/src/test/resources/SupportApache-small.png
new file mode 100644
index 0000000..4a23e05
Binary files /dev/null and b/src/test/resources/SupportApache-small.png differ
diff --git a/src/test/resources/password b/src/test/resources/password
new file mode 100644
index 0000000..ad66ce8
--- /dev/null
+++ b/src/test/resources/password
@@ -0,0 +1 @@
++AQ?aDes!'DBMkrCi:FE6q\sOn=Pbmn=PK8n=PK?
\ No newline at end of file
diff --git a/src/test/resources/sling.png b/src/test/resources/sling.png
new file mode 100644
index 0000000..69163d9
Binary files /dev/null and b/src/test/resources/sling.png differ
diff --git a/src/test/resources/template-inlines.html b/src/test/resources/template-inlines.html
new file mode 100644
index 0000000..33d4e0c
--- /dev/null
+++ b/src/test/resources/template-inlines.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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.
+-->
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Thymeleaf HTML Mail Template with Inline Image</title>
+</head>
+<body>
+<table>
+ <tr>
+ <td>This message was sent with Apache Sling Commons Messaging Mail. 📧</td>
+ </tr>
+ <tr>
+ <td><img src="cid:sling" width="124" height="63" alt="Apache Sling"></td>
+ </tr>
+ <tr>
+ <td><span data-th-text="${date}">date</span></td>
+ </tr>
+</table>
+</body>
+</html>
diff --git a/src/test/resources/template.html b/src/test/resources/template.html
new file mode 100644
index 0000000..e32bff0
--- /dev/null
+++ b/src/test/resources/template.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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.
+-->
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Thymeleaf HTML Mail Template</title>
+</head>
+<body>
+<table>
+ <tr>
+ <td>This message was sent with Apache Sling Commons Messaging Mail. 📧</td>
+ </tr>
+ <tr>
+ <td><span data-th-text="${date}">date</span></td>
+ </tr>
+</table>
+</body>
+</html>
diff --git a/src/test/resources/template.txt b/src/test/resources/template.txt
new file mode 100644
index 0000000..a66ee49
--- /dev/null
+++ b/src/test/resources/template.txt
@@ -0,0 +1,3 @@
+This message was sent with Apache Sling Commons Messaging Mail. 📧
+
+[[${date}]]