SLING-5644 Provide an messaging implementation based on Commons Email
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1738114 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c0aebe7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,19 @@
+Apache Sling Commons Messaging Mail
+===================================
+
+Provide an OSGi Configuration for `SimpleMailBuilder` or a custom `MailBuilder` to send messages using [Apache Commons Email](https://commons.apache.org/proper/commons-email/).
+
+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" : {
+ "mail.subject": <String>,
+ "mail.from": <String>,
+ "mail.smtp.hostname": <String>,
+ "mail.smtp.port": <int>,
+ "mail.smtp.username": <String>,
+ "mail.smtp.password": <String>
+ }
+}
+```
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..5f604e8
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,245 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>sling</artifactId>
+ <version>27-SNAPSHOT</version>
+ <relativePath/>
+ </parent>
+
+ <artifactId>org.apache.sling.commons.messaging.mail</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+
+ <name>Apache Sling Commons Messaging Mail</name>
+ <description>Messaging service using Commons Email to send mails.</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>
+ </properties>
+
+ <scm>
+ <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/commons/org.apache.sling.commons.messaging.mail</connection>
+ <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/commons/org.apache.sling.commons.messaging.mail</developerConnection>
+ <url>http://svn.apache.org/viewvc/sling/trunk/bundles/commons/org.apache.sling.commons.messaging.mail</url>
+ </scm>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.servicemix.tooling</groupId>
+ <artifactId>depends-maven-plugin</artifactId>
+ <version>1.2</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>generate-depends-file</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <version>2.18.1</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <systemProperties>
+ <property>
+ <name>bundle.filename</name>
+ <value>${basedir}/target/${project.build.finalName}.jar</value>
+ </property>
+ </systemProperties>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <!-- javax -->
+ <dependency>
+ <groupId>javax.inject</groupId>
+ <artifactId>javax.inject</artifactId>
+ <version>1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.mail</groupId>
+ <artifactId>javax.mail-api</artifactId>
+ <version>1.5.5</version>
+ <scope>provided</scope>
+ </dependency>
+ <!-- Sun -->
+ <dependency>
+ <groupId>com.sun.mail</groupId>
+ <artifactId>javax.mail</artifactId>
+ <version>1.5.5</version>
+ <scope>provided</scope>
+ </dependency>
+ <!-- OSGi -->
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.core</artifactId>
+ <version>6.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.annotation</artifactId>
+ <version>6.0.1</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.service.component.annotations</artifactId>
+ <version>1.3.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.service.metatype.annotations</artifactId>
+ <version>1.3.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <!-- Apache Commons -->
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-email</artifactId>
+ <version>1.4</version>
+ <scope>provided</scope>
+ </dependency>
+ <!-- Apache Felix -->
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.configadmin</artifactId>
+ <version>1.8.8</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.scr</artifactId>
+ <version>2.0.2</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>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.messaging</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <!-- logging -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <!-- JSR 305-->
+ <dependency>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <!-- testing -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-container-forked</artifactId>
+ <version>4.8.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-junit4</artifactId>
+ <version>4.8.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-link-mvn</artifactId>
+ <version>4.8.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.url</groupId>
+ <artifactId>pax-url-aether</artifactId>
+ <version>2.4.6</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.url</groupId>
+ <artifactId>pax-url-reference</artifactId>
+ <version>2.4.6</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>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/src/main/java/org/apache/sling/commons/messaging/mail/MailBuilder.java b/src/main/java/org/apache/sling/commons/messaging/mail/MailBuilder.java
new file mode 100644
index 0000000..2a5261f
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/messaging/mail/MailBuilder.java
@@ -0,0 +1,34 @@
+/*
+ * 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 java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import org.apache.commons.mail.Email;
+import org.apache.commons.mail.EmailException;
+import org.osgi.annotation.versioning.ProviderType;
+
+@ProviderType
+public interface MailBuilder {
+
+ Email build(@Nonnull final String message, @Nonnull final String recipient, @Nonnull final Map data) throws EmailException;
+
+}
diff --git a/src/main/java/org/apache/sling/commons/messaging/mail/MailUtil.java b/src/main/java/org/apache/sling/commons/messaging/mail/MailUtil.java
new file mode 100644
index 0000000..d258fe2
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/messaging/mail/MailUtil.java
@@ -0,0 +1,36 @@
+/*
+ * 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 java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import javax.mail.MessagingException;
+
+import org.apache.commons.mail.Email;
+
+public class MailUtil {
+
+ public static byte[] toByteArray(final Email email) throws IOException, MessagingException {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ email.getMimeMessage().writeTo(baos);
+ return baos.toByteArray();
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/commons/messaging/mail/internal/MailFailure.java b/src/main/java/org/apache/sling/commons/messaging/mail/internal/MailFailure.java
new file mode 100644
index 0000000..ecb837e
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/messaging/mail/internal/MailFailure.java
@@ -0,0 +1,52 @@
+/*
+ * 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 org.apache.sling.commons.messaging.Failure;
+
+public class MailFailure implements Failure {
+
+ private final String code;
+
+ private final String type;
+
+ private final String message;
+
+ public MailFailure(final String code, final String type, final String message) {
+ this.code = code;
+ this.type = type;
+ this.message = message;
+ }
+
+ @Override
+ public String getCode() {
+ return code;
+ }
+
+ @Override
+ public String getType() {
+ return type;
+ }
+
+ @Override
+ public String getMessage() {
+ return message;
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/commons/messaging/mail/internal/MailResult.java b/src/main/java/org/apache/sling/commons/messaging/mail/internal/MailResult.java
new file mode 100644
index 0000000..072b1b5
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/messaging/mail/internal/MailResult.java
@@ -0,0 +1,56 @@
+/*
+ * 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.Arrays;
+import java.util.Collection;
+
+import org.apache.sling.commons.messaging.Failure;
+import org.apache.sling.commons.messaging.Result;
+
+public class MailResult implements Result<byte[]> {
+
+ private final byte[] message;
+
+ private final Collection<Failure> failures;
+
+ public MailResult(final byte[] message, final Failure... failures) {
+ this.message = message;
+ this.failures = Arrays.asList(failures);
+ }
+
+ /**
+ * @return the message in <a href="https://tools.ietf.org/html/rfc822">RFC 822</a> format
+ */
+ @Override
+ public byte[] getMessage() {
+ return message;
+ }
+
+ @Override
+ public boolean hasFailures() {
+ return !(failures == null || failures.isEmpty());
+ }
+
+ @Override
+ public Collection<Failure> getFailures() {
+ return failures;
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailBuilder.java b/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailBuilder.java
new file mode 100644
index 0000000..e26ea71
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailBuilder.java
@@ -0,0 +1,125 @@
+/*
+ * 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.Collections;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import org.apache.commons.mail.Email;
+import org.apache.commons.mail.EmailException;
+import org.apache.commons.mail.SimpleEmail;
+import org.apache.sling.commons.messaging.mail.MailBuilder;
+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.ConfigurationPolicy;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.metatype.annotations.Designate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(
+ service = MailBuilder.class,
+ property = {
+ Constants.SERVICE_DESCRIPTION + "=Service to build simple mails.",
+ Constants.SERVICE_VENDOR + "=The Apache Software Foundation"
+ },
+ configurationPolicy = ConfigurationPolicy.REQUIRE
+)
+@Designate(
+ ocd = SimpleMailBuilderConfiguration.class
+)
+public class SimpleMailBuilder implements MailBuilder {
+
+ // TODO use encryption and support more configuration options
+
+ private String subject;
+
+ private String from;
+
+ private String smtpHostname;
+
+ private int smtpPort;
+
+ private String smtpUsername;
+
+ private String smtpPassword;
+
+ private static final String SUBJECT_KEY = "mail.subject";
+
+ private static final String FROM_KEY = "mail.from";
+
+ private static final String SMTP_HOSTNAME_KEY = "mail.smtp.hostname";
+
+ private static final String SMTP_PORT_KEY = "mail.smtp.port";
+
+ private static final String SMTP_USERNAME_KEY = "mail.smtp.username";
+
+ private static final String SMTP_PASSWORD_KEY = "mail.smtp.password";
+
+ private final Logger logger = LoggerFactory.getLogger(SimpleMailBuilder.class);
+
+ public SimpleMailBuilder() {
+ }
+
+ @Activate
+ private void activate(final SimpleMailBuilderConfiguration configuration) {
+ logger.debug("activate");
+ configure(configuration);
+ }
+
+ @Modified
+ private void modified(final SimpleMailBuilderConfiguration configuration) {
+ logger.debug("modified");
+ configure(configuration);
+ }
+
+ private void configure(final SimpleMailBuilderConfiguration configuration) {
+ subject = configuration.subject();
+ from = configuration.from();
+ smtpHostname = configuration.smtpHostname();
+ smtpPort = configuration.smtpPort();
+ smtpUsername = configuration.smtpUsername();
+ smtpPassword = configuration.smtpPassword();
+ }
+
+ @Override
+ public Email build(@Nonnull final String message, @Nonnull final String recipient, @Nonnull final Map data) throws EmailException {
+ final Map configuration = (Map) data.getOrDefault("mail", Collections.EMPTY_MAP);
+ final String subject = (String) configuration.getOrDefault(SUBJECT_KEY, this.subject);
+ final String from = (String) configuration.getOrDefault(FROM_KEY, this.from);
+ final String smtpHostname = (String) configuration.getOrDefault(SMTP_HOSTNAME_KEY, this.smtpHostname);
+ final int smtpPort = (Integer) configuration.getOrDefault(SMTP_PORT_KEY, this.smtpPort);
+ final String smtpUsername = (String) configuration.getOrDefault(SMTP_USERNAME_KEY, this.smtpUsername);
+ final String smtpPassword = (String) configuration.getOrDefault(SMTP_PASSWORD_KEY, this.smtpPassword);
+
+ final Email email = new SimpleEmail();
+ email.setMsg(message);
+ email.addTo(recipient);
+ email.setSubject(subject);
+ email.setFrom(from);
+ email.setHostName(smtpHostname);
+ email.setSmtpPort(smtpPort);
+ email.setAuthentication(smtpUsername, smtpPassword);
+ return email;
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailBuilderConfiguration.java b/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailBuilderConfiguration.java
new file mode 100644
index 0000000..5b63185
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailBuilderConfiguration.java
@@ -0,0 +1,66 @@
+/*
+ * 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 org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+@ObjectClassDefinition(
+ name = "Apache Sling Commons Messaging Mail “Simple Mail Builder”",
+ description = "simple mail builder for Sling Commons Messaging Mail"
+)
+@interface SimpleMailBuilderConfiguration {
+
+ @AttributeDefinition(
+ name = "subject",
+ description = "default subject for mails"
+ )
+ String subject();
+
+ @AttributeDefinition(
+ name = "from",
+ description = "default from (sender) address for mails"
+ )
+ String from();
+
+ @AttributeDefinition(
+ name = "SMTP hostname",
+ description = "hostname of SMTP server"
+ )
+ String smtpHostname() default "localhost";
+
+ @AttributeDefinition(
+ name = "SMTP port",
+ description = "port of SMTP server"
+ )
+ int smtpPort() default 25;
+
+ @AttributeDefinition(
+ name = "SMTP username",
+ description = "username for SMTP server"
+ )
+ String smtpUsername();
+
+ @AttributeDefinition(
+ name = "SMTP password",
+ description = "password for SMTP server"
+ )
+ String smtpPassword();
+
+}
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
new file mode 100644
index 0000000..b79eef1
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailService.java
@@ -0,0 +1,160 @@
+/*
+ * 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.Collections;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
+
+import org.apache.commons.mail.Email;
+import org.apache.sling.commons.messaging.Failure;
+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.MailUtil;
+import org.apache.sling.commons.threads.ThreadPool;
+import org.apache.sling.commons.threads.ThreadPoolManager;
+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.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+import org.osgi.service.metatype.annotations.Designate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(
+ service = MessageService.class,
+ property = {
+ Constants.SERVICE_DESCRIPTION + "=Service to send messages by mail.",
+ Constants.SERVICE_VENDOR + "=The Apache Software Foundation"
+ }
+)
+@Designate(
+ ocd = SimpleMailServiceConfiguration.class
+)
+public class SimpleMailService implements MessageService {
+
+ @Reference(
+ cardinality = ReferenceCardinality.MANDATORY,
+ policy = ReferencePolicy.DYNAMIC,
+ policyOption = ReferencePolicyOption.GREEDY
+ )
+ private volatile MailBuilder mailBuilder;
+
+ @Reference(
+ cardinality = ReferenceCardinality.MANDATORY,
+ policy = ReferencePolicy.DYNAMIC,
+ policyOption = ReferencePolicyOption.GREEDY
+ )
+ private volatile ThreadPoolManager threadPoolManager;
+
+ // the ThreadPool used for sending mails
+ private ThreadPool threadPool;
+
+ private final Logger logger = LoggerFactory.getLogger(SimpleMailService.class);
+
+ public SimpleMailService() {
+ }
+
+ @Activate
+ private void activate(final SimpleMailServiceConfiguration configuration) {
+ logger.debug("activate");
+ configure(configuration);
+ }
+
+ @Modified
+ private void modified(final SimpleMailServiceConfiguration configuration) {
+ logger.debug("modified");
+ configure(configuration);
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ logger.info("deactivate");
+ threadPoolManager.release(threadPool);
+ threadPool = null;
+ }
+
+ private void configure(final SimpleMailServiceConfiguration configuration) {
+ threadPoolManager.release(threadPool);
+ threadPool = threadPoolManager.get(configuration.threadpoolName());
+ }
+
+ @Override
+ public Future<Result> send(final String message, final String recipient) {
+ return send(message, recipient, Collections.emptyMap());
+ }
+
+ @Override
+ public Future<Result> send(final String message, final String recipient, final Map data) {
+ final Mailing mailing = new Mailing(message, recipient, data, mailBuilder);
+ return threadPool.submit(mailing);
+ }
+
+ private class Mailing implements Callable<Result> {
+
+ private final String message;
+
+ private final String recipient;
+
+ private final Map data;
+
+ private MailBuilder mailBuilder;
+
+ Mailing(final String message, final String recipient, final Map data, final MailBuilder mailBuilder) {
+ this.message = message;
+ this.recipient = recipient;
+ this.data = data;
+ this.mailBuilder = mailBuilder;
+ }
+
+ @Override
+ public Result call() {
+ Email mail = null;
+ // build mail
+ try {
+ mail = mailBuilder.build(message, recipient, data);
+ } catch (Exception e) {
+ logger.error("building mail failed: {}", e.getMessage(), e);
+ final Failure failure = new MailFailure(null, null, e.getMessage());
+ return new MailResult(null, failure);
+ } finally {
+ mailBuilder = null;
+ }
+ // send mail
+ try {
+ final String messageId = mail.send();
+ logger.info("mail '{}' sent", messageId);
+ final byte[] bytes = MailUtil.toByteArray(mail);
+ return new MailResult(bytes);
+ } catch (Exception e) {
+ logger.error("sending mail failed: {}", e.getMessage(), e);
+ final Failure failure = new MailFailure(null, null, e.getMessage());
+ return new MailResult(null, failure);
+ }
+ }
+ }
+
+}
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
new file mode 100644
index 0000000..493041b
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailServiceConfiguration.java
@@ -0,0 +1,36 @@
+/*
+ * 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 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"
+)
+@interface SimpleMailServiceConfiguration {
+
+ @AttributeDefinition(
+ name = "ThreadPool name",
+ description = "name of the ThreadPool to use for sending mails"
+ )
+ String threadpoolName() default "default";
+
+}
diff --git a/src/main/java/org/apache/sling/commons/messaging/mail/package-info.java b/src/main/java/org/apache/sling/commons/messaging/mail/package-info.java
new file mode 100644
index 0000000..b72b0c3
--- /dev/null
+++ b/src/main/java/org/apache/sling/commons/messaging/mail/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+@Version("1.0.0")
+package org.apache.sling.commons.messaging.mail;
+
+import org.osgi.annotation.versioning.Version;
diff --git a/src/test/java/org/apache/sling/commons/messaging/mail/MailBuilderConfigurations.java b/src/test/java/org/apache/sling/commons/messaging/mail/MailBuilderConfigurations.java
new file mode 100644
index 0000000..8950f28
--- /dev/null
+++ b/src/test/java/org/apache/sling/commons/messaging/mail/MailBuilderConfigurations.java
@@ -0,0 +1,52 @@
+/*
+ * 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 java.util.Dictionary;
+import java.util.Hashtable;
+
+public class MailBuilderConfigurations {
+
+ /**
+ * @return minimal configuration properties for building mails
+ */
+ public static Dictionary<String, Object> minimal() {
+ final Dictionary<String, Object> properties = new Hashtable<>();
+ properties.put("subject", "Rudy, A Message to You");
+ properties.put("from", "dandy.livingstone@kingston.jamaica");
+ properties.put("smtpHostname", "localhost");
+ return properties;
+ }
+
+ /**
+ * @param smtpPort SMTP port to use for sending
+ * @return configuration properties including authentication for sending
+ */
+ public static Dictionary<String, Object> full(final int smtpPort) {
+ final Dictionary<String, Object> properties = new Hashtable<>();
+ properties.put("subject", "Testing the Simple Mail Service");
+ properties.put("from", "sender@example.net");
+ properties.put("smtpHostname", "localhost");
+ properties.put("smtpPort", smtpPort);
+ properties.put("smtpUsername", "test");
+ properties.put("smtpPassword", "test");
+ return properties;
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/commons/messaging/mail/MailTestSupport.java b/src/test/java/org/apache/sling/commons/messaging/mail/MailTestSupport.java
new file mode 100644
index 0000000..6ba0f9f
--- /dev/null
+++ b/src/test/java/org/apache/sling/commons/messaging/mail/MailTestSupport.java
@@ -0,0 +1,95 @@
+/*
+ * 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 java.io.IOException;
+import java.net.ServerSocket;
+import java.util.Dictionary;
+
+import javax.inject.Inject;
+
+import org.ops4j.pax.exam.Option;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.ops4j.pax.exam.CoreOptions.bundle;
+import static org.ops4j.pax.exam.CoreOptions.junitBundles;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.provision;
+import static org.ops4j.pax.exam.CoreOptions.wrappedBundle;
+
+public abstract class MailTestSupport {
+
+ @Inject
+ protected BundleContext bundleContext;
+
+ @Inject
+ protected ConfigurationAdmin configurationAdmin;
+
+ protected final Logger logger = LoggerFactory.getLogger(getClass());
+
+ public MailTestSupport() {
+ }
+
+ protected synchronized int findFreePort() {
+ try {
+ final ServerSocket serverSocket = new ServerSocket(0);
+ final int port = serverSocket.getLocalPort();
+ serverSocket.close();
+ return port;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected void createFactoryConfiguration(final String factoryPid, final Dictionary<String, Object> properties) throws IOException, InterruptedException {
+ final org.osgi.service.cm.Configuration configuration = configurationAdmin.createFactoryConfiguration(factoryPid);
+ configuration.setBundleLocation(null);
+ configuration.update(properties);
+ logger.debug("configuration: {}", configurationAdmin.getConfiguration(factoryPid));
+ }
+
+ protected <T> T getService(Class<T> type) {
+ final ServiceReference<T> serviceReference = bundleContext.getServiceReference(type);
+ return bundleContext.getService(serviceReference);
+ }
+
+ protected Option[] baseConfiguration() {
+ final String filename = System.getProperty("bundle.filename");
+ return options(
+ junitBundles(),
+ provision(
+ wrappedBundle(mavenBundle().groupId("org.subethamail").artifactId("subethasmtp").versionAsInProject()),
+ mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.configadmin").versionAsInProject(),
+ mavenBundle().groupId("org.apache.felix").artifactId("org.apache.felix.scr").versionAsInProject(),
+ mavenBundle().groupId("com.sun.mail").artifactId("javax.mail").versionAsInProject(),
+ mavenBundle().groupId("javax.mail").artifactId("javax.mail-api").versionAsInProject(),
+ mavenBundle().groupId("org.apache.commons").artifactId("commons-email").versionAsInProject(),
+ mavenBundle().groupId("org.apache.sling").artifactId("org.apache.sling.commons.messaging").versionAsInProject(),
+ mavenBundle().groupId("org.apache.sling").artifactId("org.apache.sling.commons.threads").versionAsInProject(),
+ bundle("reference:file:" + filename)
+ )
+ );
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailBuilderIT.java b/src/test/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailBuilderIT.java
new file mode 100644
index 0000000..03de1de
--- /dev/null
+++ b/src/test/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailBuilderIT.java
@@ -0,0 +1,94 @@
+/*
+ * 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.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.apache.commons.mail.Email;
+import org.apache.sling.commons.messaging.mail.MailBuilder;
+import org.apache.sling.commons.messaging.mail.MailBuilderConfigurations;
+import org.apache.sling.commons.messaging.mail.MailTestSupport;
+import org.apache.sling.commons.messaging.mail.MailUtil;
+import org.junit.Before;
+import org.junit.Test;
+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.PerMethod;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerMethod.class)
+public class SimpleMailBuilderIT extends MailTestSupport {
+
+ @Configuration
+ public Option[] configuration() {
+ return baseConfiguration();
+ }
+
+ @Before
+ public void setup() throws Exception {
+ final String factoryPid = "org.apache.sling.commons.messaging.mail.internal.SimpleMailBuilder";
+ final Dictionary<String, Object> properties = MailBuilderConfigurations.minimal();
+ createFactoryConfiguration(factoryPid, properties);
+ }
+
+ @Test
+ public void testBuildWithDefaults() throws Exception {
+ final MailBuilder mailBuilder = getService(MailBuilder.class);
+ final Email email = mailBuilder.build("Stop your messing around, Better think of your future...", "rudy@ghosttown", Collections.emptyMap());
+ email.buildMimeMessage();
+ final byte[] bytes = MailUtil.toByteArray(email);
+ final String mail = new String(bytes, StandardCharsets.UTF_8);
+ logger.debug("mail: " + mail);
+ assertEquals("rudy@ghosttown", email.getToAddresses().get(0).getAddress());
+ assertEquals("Rudy, A Message to You", email.getSubject());
+ assertEquals("dandy.livingstone@kingston.jamaica", email.getFromAddress().getAddress());
+ assertEquals("localhost", email.getHostName());
+ logger.debug(email.getMimeMessage().getContent().toString());
+ }
+
+ @Test
+ public void testBuildWithData() throws Exception {
+ final MailBuilder mailBuilder = getService(MailBuilder.class);
+ final Map<String, String> configuration = new HashMap<>();
+ configuration.put("mail.subject", "Rudy, A Message to You");
+ configuration.put("mail.from", "specials@thespecials.com");
+ final Map data = Collections.singletonMap("mail", configuration);
+ final Email email = mailBuilder.build("A Message to You, Rudy", "rudy@ghosttown", data);
+ email.buildMimeMessage();
+ final byte[] bytes = MailUtil.toByteArray(email);
+ final String mail = new String(bytes, StandardCharsets.UTF_8);
+ logger.debug("mail: " + mail);
+ assertEquals("rudy@ghosttown", email.getToAddresses().get(0).getAddress());
+ assertEquals("Rudy, A Message to You", email.getSubject());
+ assertEquals("specials@thespecials.com", email.getFromAddress().getAddress());
+ assertEquals("localhost", email.getHostName());
+ logger.debug(email.getMimeMessage().getContent().toString());
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailServiceIT.java b/src/test/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailServiceIT.java
new file mode 100644
index 0000000..85fd000
--- /dev/null
+++ b/src/test/java/org/apache/sling/commons/messaging/mail/internal/SimpleMailServiceIT.java
@@ -0,0 +1,88 @@
+/*
+ * 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.Collections;
+import java.util.Dictionary;
+import java.util.Map;
+import java.util.concurrent.Future;
+
+import org.apache.sling.commons.messaging.MessageService;
+import org.apache.sling.commons.messaging.Result;
+import org.apache.sling.commons.messaging.mail.MailBuilderConfigurations;
+import org.apache.sling.commons.messaging.mail.MailTestSupport;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+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.PerMethod;
+import org.subethamail.wiser.Wiser;
+
+import static junit.framework.TestCase.assertFalse;
+
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerMethod.class)
+public class SimpleMailServiceIT extends MailTestSupport {
+
+ protected Wiser wiser;
+
+ @Configuration
+ public Option[] configuration() {
+ return baseConfiguration();
+ }
+
+ @Before
+ public void setup() throws Exception {
+ final int smtpPort = findFreePort();
+ wiser = new Wiser(smtpPort);
+ wiser.start();
+ final String factoryPid = "org.apache.sling.commons.messaging.mail.internal.SimpleMailBuilder";
+ final Dictionary<String, Object> properties = MailBuilderConfigurations.full(smtpPort);
+ createFactoryConfiguration(factoryPid, properties);
+ }
+
+ @After
+ public void teardown() {
+ wiser.stop();
+ wiser = null;
+ }
+
+ @Test
+ public void send() throws Exception {
+ final MessageService messageService = getService(MessageService.class);
+ final Future<Result> future = messageService.send("simple test message", "recipient@example.net");
+ final Result result = future.get();
+ assertFalse(result.hasFailures());
+ }
+
+ @Test
+ public void sendWithData() throws Exception {
+ final MessageService messageService = getService(MessageService.class);
+ final Map configuration = Collections.singletonMap("mail.subject", "Testing the Simple Mail Service with a custom subject");
+ final Map data = Collections.singletonMap("mail", configuration);
+ final Future<Result> future = messageService.send("simple test message", "recipient@example.net", data);
+ final Result result = future.get();
+ assertFalse(result.hasFailures());
+ }
+
+}