This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-commons-messaging-mail.git
commit d65096753e400bba824f03a1b89795a895092ed0 Author: Oliver Lietz <[email protected]> AuthorDate: Thu Apr 7 09:18:07 2016 +0000 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 --- README.md | 19 ++ pom.xml | 245 +++++++++++++++++++++ .../sling/commons/messaging/mail/MailBuilder.java | 34 +++ .../sling/commons/messaging/mail/MailUtil.java | 36 +++ .../messaging/mail/internal/MailFailure.java | 52 +++++ .../messaging/mail/internal/MailResult.java | 56 +++++ .../messaging/mail/internal/SimpleMailBuilder.java | 125 +++++++++++ .../internal/SimpleMailBuilderConfiguration.java | 66 ++++++ .../messaging/mail/internal/SimpleMailService.java | 160 ++++++++++++++ .../internal/SimpleMailServiceConfiguration.java | 36 +++ .../sling/commons/messaging/mail/package-info.java | 22 ++ .../messaging/mail/MailBuilderConfigurations.java | 52 +++++ .../commons/messaging/mail/MailTestSupport.java | 95 ++++++++ .../mail/internal/SimpleMailBuilderIT.java | 94 ++++++++ .../mail/internal/SimpleMailServiceIT.java | 88 ++++++++ 15 files changed, 1180 insertions(+) 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", "[email protected]"); + 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", "[email protected]"); + 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("[email protected]", 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", "[email protected]"); + 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("[email protected]", 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", "[email protected]"); + 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", "[email protected]", data); + final Result result = future.get(); + assertFalse(result.hasFailures()); + } + +} -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
