http://git-wip-us.apache.org/repos/asf/james-project/blob/52c18ef6/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/remoteDelivery/RemoteDeliverySocketFactory.java
----------------------------------------------------------------------
diff --git 
a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/remoteDelivery/RemoteDeliverySocketFactory.java
 
b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/remoteDelivery/RemoteDeliverySocketFactory.java
deleted file mode 100644
index d4a5ba5..0000000
--- 
a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/remoteDelivery/RemoteDeliverySocketFactory.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/****************************************************************
- * 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.james.transport.mailets.remoteDelivery;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.net.UnknownHostException;
-
-import javax.net.SocketFactory;
-
-/**
- * <p>
- * It is used by RemoteDelivery in order to make possible to bind the client
- * socket to a specific ip address.
- * </p>
- * <p>
- * This is not a nice solution because the ip address must be shared by all
- * RemoteDelivery instances. It would be better to modify JavaMail (current
- * version 1.3) to support a corresonding property, e.g. mail.smtp.bindAdress.
- * </p>
- * <p>
- * This used to not extend javax.net.SocketFactory descendant, because
- * <ol>
- * <li>
- * it was not necessary because JavaMail 1.2 uses reflection when accessing 
this
- * class;</li>
- * <li>
- * it was not desirable because it would require java 1.4.</li>
- * </ol>
- * </p>
- * <p>
- * But since James 2.3.0a1:
- * <ol>
- * <li>we require Java 1.4 so the dependency on SocketFactory is not really an
- * issue;</li>
- * <li>Javamail 1.4 cast the object returned by getDefault to SocketFactory and
- * fails to create the socket if we don't extend SocketFactory.</li>
- * </ol>
- * </p>
- * <p>
- * <strong>Note</strong>: Javamail 1.4 should correctly support
- * mail.smtp.localaddr so we could probably get rid of this class and simply 
add
- * that property to the Session.
- * </p>
- */
-public class RemoteDeliverySocketFactory extends SocketFactory {
-
-    /**
-     * @param addr
-     *            the ip address or host name the delivery socket will bind to
-     */
-    public static void setBindAdress(String addr) throws UnknownHostException {
-        if (addr == null) {
-            bindAddress = null;
-        } else {
-            bindAddress = InetAddress.getByName(addr);
-        }
-    }
-
-    /**
-     * the same as the similarly named javax.net.SocketFactory operation.
-     */
-    public static SocketFactory getDefault() {
-        return new RemoteDeliverySocketFactory();
-    }
-
-    /**
-     * the same as the similarly named javax.net.SocketFactory operation. Just
-     * to be safe, it is not used by JavaMail 1.3. This is the only method used
-     * by JavaMail 1.4.
-     */
-    public Socket createSocket() throws IOException {
-        Socket s = new Socket();
-        s.bind(new InetSocketAddress(bindAddress, 0));
-        return s;
-    }
-
-    /**
-     * the same as the similarly named javax.net.SocketFactory operation. This
-     * is the one which is used by JavaMail 1.3. This is not used by JavaMail
-     * 1.4.
-     */
-    public Socket createSocket(String host, int port) throws IOException {
-        return new Socket(host, port, bindAddress, 0);
-    }
-
-    /**
-     * the same as the similarly named javax.net.SocketFactory operation. Just
-     * to be safe, it is not used by JavaMail 1.3. This is not used by JavaMail
-     * 1.4.
-     */
-    public Socket createSocket(String host, int port, InetAddress clientHost, 
int clientPort) throws IOException {
-        return new Socket(host, port, clientHost == null ? bindAddress : 
clientHost, clientPort);
-    }
-
-    /**
-     * the same as the similarly named javax.net.SocketFactory operation. Just
-     * to be safe, it is not used by JavaMail 1.3. This is not used by JavaMail
-     * 1.4.
-     */
-    public Socket createSocket(InetAddress host, int port) throws IOException {
-        return new Socket(host, port, bindAddress, 0);
-    }
-
-    /**
-     * the same as the similarly named javax.net.SocketFactory operation. Just
-     * to be safe, it is not used by JavaMail 1.3. This is not used by JavaMail
-     * 1.4.
-     */
-    public Socket createSocket(InetAddress address, int port, InetAddress 
clientAddress, int clientPort) throws IOException {
-        return new Socket(address, port, clientAddress == null ? bindAddress : 
clientAddress, clientPort);
-    }
-
-    /**
-     * it should be set by setBindAdress(). Null means the socket is bind to 
the
-     * default address.
-     */
-    private static InetAddress bindAddress;
-}

http://git-wip-us.apache.org/repos/asf/james-project/blob/52c18ef6/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/remoteDelivery/Repeat.java
----------------------------------------------------------------------
diff --git 
a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/remoteDelivery/Repeat.java
 
b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/remoteDelivery/Repeat.java
deleted file mode 100644
index 5c26342..0000000
--- 
a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/remoteDelivery/Repeat.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/****************************************************************
- * 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.james.transport.mailets.remoteDelivery;
-
-import java.util.List;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-
-public class Repeat {
-
-    @SuppressWarnings("unchecked")
-    public static <T> List<T> repeat(T element, int times) {
-        Preconditions.checkArgument(times >= 0, "Times argument should be 
strictly positive");
-        return ImmutableList.copyOf(
-            Iterables.limit(
-                Iterables.cycle(element), times));
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/james-project/blob/52c18ef6/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/AddressesArrayToMailAddressListConverterTest.java
----------------------------------------------------------------------
diff --git 
a/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/AddressesArrayToMailAddressListConverterTest.java
 
b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/AddressesArrayToMailAddressListConverterTest.java
new file mode 100644
index 0000000..b54f940
--- /dev/null
+++ 
b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/AddressesArrayToMailAddressListConverterTest.java
@@ -0,0 +1,67 @@
+/****************************************************************
+ * 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.james.transport.mailets.remote.delivery;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import javax.mail.Address;
+import javax.mail.internet.InternetAddress;
+
+import 
org.apache.james.transport.mailets.remote.delivery.AddressesArrayToMailAddressListConverter;
+import org.apache.mailet.base.MailAddressFixture;
+import org.junit.Test;
+
+public class AddressesArrayToMailAddressListConverterTest {
+
+    private static final String WRONG_INTERNET_ADDRESS = "!!";
+
+    @Test
+    public void getAddressesAsMailAddressShouldReturnEmptyOnNull() {
+        
assertThat(AddressesArrayToMailAddressListConverter.getAddressesAsMailAddress(null)).isEmpty();
+    }
+
+    @Test
+    public void getAddressesAsMailAddressShouldReturnEmptyOnEmpty() {
+        
assertThat(AddressesArrayToMailAddressListConverter.getAddressesAsMailAddress(new
 Address[]{})).isEmpty();
+    }
+
+    @Test
+    public void getAddressesAsMailAddressShouldWorkWithSingleValue() throws 
Exception {
+        
assertThat(AddressesArrayToMailAddressListConverter.getAddressesAsMailAddress(new
 Address[]{
+            new InternetAddress(MailAddressFixture.ANY_AT_JAMES.toString())}))
+            .containsOnly(MailAddressFixture.ANY_AT_JAMES);
+    }
+
+    @Test
+    public void getAddressesAsMailAddressShouldWorkWithTwoValues() throws 
Exception {
+        
assertThat(AddressesArrayToMailAddressListConverter.getAddressesAsMailAddress(new
 Address[]{
+            new InternetAddress(MailAddressFixture.ANY_AT_JAMES.toString()),
+            new 
InternetAddress(MailAddressFixture.OTHER_AT_JAMES.toString())}))
+            .containsOnly(MailAddressFixture.ANY_AT_JAMES, 
MailAddressFixture.OTHER_AT_JAMES);
+    }
+
+    @Test
+    public void getAddressesAsMailAddressShouldFilterErrorMailAddress() throws 
Exception {
+        
assertThat(AddressesArrayToMailAddressListConverter.getAddressesAsMailAddress(new
 Address[]{
+            new InternetAddress(MailAddressFixture.ANY_AT_JAMES.toString()),
+            new InternetAddress(WRONG_INTERNET_ADDRESS)}))
+            .containsOnly(MailAddressFixture.ANY_AT_JAMES);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/52c18ef6/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/BouncerTest.java
----------------------------------------------------------------------
diff --git 
a/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/BouncerTest.java
 
b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/BouncerTest.java
new file mode 100644
index 0000000..4bac706
--- /dev/null
+++ 
b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/BouncerTest.java
@@ -0,0 +1,456 @@
+/****************************************************************
+ * 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.james.transport.mailets.remote.delivery;
+
+import static 
org.apache.james.transport.mailets.remote.delivery.Bouncer.DELIVERY_ERROR;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+import java.net.ConnectException;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Optional;
+
+import javax.mail.MessagingException;
+import javax.mail.SendFailedException;
+
+import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.transport.mailets.remote.delivery.Bouncer;
+import 
org.apache.james.transport.mailets.remote.delivery.RemoteDeliveryConfiguration;
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.MailAddressFixture;
+import org.apache.mailet.base.test.FakeMail;
+import org.apache.mailet.base.test.FakeMailContext;
+import org.apache.mailet.base.test.FakeMailetConfig;
+import org.junit.Before;
+import org.junit.Test;
+
+public class BouncerTest {
+    public static final String HELLO_NAME = "hello_name";
+    public static final String BOUNCE_PROCESSOR = "bounce_processor";
+
+    private FakeMailContext mailetContext;
+
+    @Before
+    public void setUp() {
+        mailetContext = FakeMailContext.defaultContext();
+    }
+
+    @Test
+    public void bounceShouldCallMailetContextBounceByDefault() throws 
Exception {
+        RemoteDeliveryConfiguration configuration = new 
RemoteDeliveryConfiguration(
+            FakeMailetConfig.builder()
+                .setProperty(RemoteDeliveryConfiguration.DELIVERY_THREADS, "1")
+                .setProperty(RemoteDeliveryConfiguration.HELO_NAME, HELLO_NAME)
+                .build(),
+            mock(DomainList.class));
+        Bouncer testee = new Bouncer(configuration, mailetContext);
+
+        Mail mail = FakeMail.builder().state(Mail.DEFAULT)
+            .sender(MailAddressFixture.ANY_AT_JAMES)
+            .build();
+        testee.bounce(mail, new Exception("Exception message"));
+
+        FakeMailContext.BouncedMail expected = new 
FakeMailContext.BouncedMail(FakeMailContext.fromMail(mail),
+            "Hi. This is the James mail server at " + HELLO_NAME + ".\n" +
+                "I'm afraid I wasn't able to deliver your message to the 
following addresses.\n" +
+                "This is a permanent error; I've given up. Sorry it didn't 
work out. Below\n" +
+                "I include the list of recipients and the reason why I was 
unable to deliver\n" +
+                "your message.\n\n\n",
+            Optional.empty());
+        assertThat(mailetContext.getSentMails()).isEmpty();
+        assertThat(mailetContext.getBouncedMails()).containsOnly(expected);
+    }
+
+    @Test
+    public void bounceShouldIncludeMessagingExceptionMessageByDefault() throws 
Exception {
+        RemoteDeliveryConfiguration configuration = new 
RemoteDeliveryConfiguration(
+            FakeMailetConfig.builder()
+                .setProperty(RemoteDeliveryConfiguration.DELIVERY_THREADS, "1")
+                .setProperty(RemoteDeliveryConfiguration.HELO_NAME, HELLO_NAME)
+                .build(),
+            mock(DomainList.class));
+        Bouncer testee = new Bouncer(configuration, mailetContext);
+
+        Mail mail = FakeMail.builder().state(Mail.DEFAULT)
+            .sender(MailAddressFixture.ANY_AT_JAMES)
+            .build();
+        String exceptionMessage = "Exception message";
+        testee.bounce(mail, new MessagingException(exceptionMessage));
+
+        FakeMailContext.BouncedMail expected = new 
FakeMailContext.BouncedMail(FakeMailContext.fromMail(mail),
+            "Hi. This is the James mail server at " + HELLO_NAME + ".\n" +
+                "I'm afraid I wasn't able to deliver your message to the 
following addresses.\n" +
+                "This is a permanent error; I've given up. Sorry it didn't 
work out. Below\n" +
+                "I include the list of recipients and the reason why I was 
unable to deliver\n" +
+                "your message.\n" +
+                "\n" +
+                exceptionMessage + "\n\n",
+            Optional.empty());
+        assertThat(mailetContext.getSentMails()).isEmpty();
+        assertThat(mailetContext.getBouncedMails()).containsOnly(expected);
+    }
+
+    @Test
+    public void bounceShouldCustomizeSendFailedExceptionByDefault() throws 
Exception {
+        RemoteDeliveryConfiguration configuration = new 
RemoteDeliveryConfiguration(
+            FakeMailetConfig.builder()
+                .setProperty(RemoteDeliveryConfiguration.DELIVERY_THREADS, "1")
+                .setProperty(RemoteDeliveryConfiguration.HELO_NAME, HELLO_NAME)
+                .build(),
+            mock(DomainList.class));
+        Bouncer testee = new Bouncer(configuration, mailetContext);
+
+        Mail mail = FakeMail.builder().state(Mail.DEFAULT)
+            .sender(MailAddressFixture.ANY_AT_JAMES)
+            .build();
+        String exceptionMessage = "Error from remote server";
+        testee.bounce(mail, new MessagingException("Exception message", new 
SendFailedException(exceptionMessage)));
+
+        FakeMailContext.BouncedMail expected = new 
FakeMailContext.BouncedMail(FakeMailContext.fromMail(mail),
+            "Hi. This is the James mail server at " + HELLO_NAME + ".\n" +
+                "I'm afraid I wasn't able to deliver your message to the 
following addresses.\n" +
+                "This is a permanent error; I've given up. Sorry it didn't 
work out. Below\n" +
+                "I include the list of recipients and the reason why I was 
unable to deliver\n" +
+                "your message.\n" +
+                "\n" +
+                "Remote mail server told me: " + exceptionMessage + "\n\n",
+            Optional.empty());
+        assertThat(mailetContext.getSentMails()).isEmpty();
+        assertThat(mailetContext.getBouncedMails()).containsOnly(expected);
+    }
+
+    @Test
+    public void bounceShouldCustomizeUnknownHostExceptionByDefault() throws 
Exception {
+        RemoteDeliveryConfiguration configuration = new 
RemoteDeliveryConfiguration(
+            FakeMailetConfig.builder()
+                .setProperty(RemoteDeliveryConfiguration.DELIVERY_THREADS, "1")
+                .setProperty(RemoteDeliveryConfiguration.HELO_NAME, HELLO_NAME)
+                .build(),
+            mock(DomainList.class));
+        Bouncer testee = new Bouncer(configuration, mailetContext);
+
+        Mail mail = FakeMail.builder().state(Mail.DEFAULT)
+            .sender(MailAddressFixture.ANY_AT_JAMES)
+            .build();
+        String exceptionMessage = "I don't know him";
+        testee.bounce(mail, new MessagingException("Exception message", new 
UnknownHostException(exceptionMessage)));
+
+        FakeMailContext.BouncedMail expected = new 
FakeMailContext.BouncedMail(FakeMailContext.fromMail(mail),
+            "Hi. This is the James mail server at " + HELLO_NAME + ".\n" +
+                "I'm afraid I wasn't able to deliver your message to the 
following addresses.\n" +
+                "This is a permanent error; I've given up. Sorry it didn't 
work out. Below\n" +
+                "I include the list of recipients and the reason why I was 
unable to deliver\n" +
+                "your message.\n" +
+                "\n" +
+                "Unknown host: " + exceptionMessage + "\n" +
+                "This could be a DNS server error, a typo, or a problem with 
the recipient's mail server.\n\n",
+            Optional.empty());
+        assertThat(mailetContext.getSentMails()).isEmpty();
+        assertThat(mailetContext.getBouncedMails()).containsOnly(expected);
+    }
+
+    @Test
+    public void bounceShouldCustomizeConnectionExceptionByDefault() throws 
Exception {
+        RemoteDeliveryConfiguration configuration = new 
RemoteDeliveryConfiguration(
+            FakeMailetConfig.builder()
+                .setProperty(RemoteDeliveryConfiguration.DELIVERY_THREADS, "1")
+                .setProperty(RemoteDeliveryConfiguration.HELO_NAME, HELLO_NAME)
+                .build(),
+            mock(DomainList.class));
+        Bouncer testee = new Bouncer(configuration, mailetContext);
+
+        Mail mail = FakeMail.builder().state(Mail.DEFAULT)
+            .sender(MailAddressFixture.ANY_AT_JAMES)
+            .build();
+        String exceptionMessage = "Can not connect";
+        testee.bounce(mail, new MessagingException("Exception message", new 
ConnectException(exceptionMessage)));
+
+        FakeMailContext.BouncedMail expected = new 
FakeMailContext.BouncedMail(FakeMailContext.fromMail(mail),
+            "Hi. This is the James mail server at " + HELLO_NAME + ".\n" +
+                "I'm afraid I wasn't able to deliver your message to the 
following addresses.\n" +
+                "This is a permanent error; I've given up. Sorry it didn't 
work out. Below\n" +
+                "I include the list of recipients and the reason why I was 
unable to deliver\n" +
+                "your message.\n" +
+                "\n" +
+                exceptionMessage + "\n\n",
+            Optional.empty());
+        assertThat(mailetContext.getSentMails()).isEmpty();
+        assertThat(mailetContext.getBouncedMails()).containsOnly(expected);
+    }
+
+    @Test
+    public void bounceShouldCustomizeSocketExceptionByDefault() throws 
Exception {
+        RemoteDeliveryConfiguration configuration = new 
RemoteDeliveryConfiguration(
+            FakeMailetConfig.builder()
+                .setProperty(RemoteDeliveryConfiguration.DELIVERY_THREADS, "1")
+                .setProperty(RemoteDeliveryConfiguration.HELO_NAME, HELLO_NAME)
+                .build(),
+            mock(DomainList.class));
+        Bouncer testee = new Bouncer(configuration, mailetContext);
+
+        Mail mail = FakeMail.builder().state(Mail.DEFAULT)
+            .sender(MailAddressFixture.ANY_AT_JAMES)
+            .build();
+        String exceptionMessage = "Can not connect";
+        testee.bounce(mail, new MessagingException("Exception message", new 
SocketException(exceptionMessage)));
+
+        FakeMailContext.BouncedMail expected = new 
FakeMailContext.BouncedMail(FakeMailContext.fromMail(mail),
+            "Hi. This is the James mail server at " + HELLO_NAME + ".\n" +
+                "I'm afraid I wasn't able to deliver your message to the 
following addresses.\n" +
+                "This is a permanent error; I've given up. Sorry it didn't 
work out. Below\n" +
+                "I include the list of recipients and the reason why I was 
unable to deliver\n" +
+                "your message.\n" +
+                "\n" +
+                "Socket exception: " + exceptionMessage + "\n\n",
+            Optional.empty());
+        assertThat(mailetContext.getSentMails()).isEmpty();
+        assertThat(mailetContext.getBouncedMails()).containsOnly(expected);
+    }
+
+    @Test
+    public void bounceShouldCustomizeNestedMessagingExceptionByDefault() 
throws Exception {
+        RemoteDeliveryConfiguration configuration = new 
RemoteDeliveryConfiguration(
+            FakeMailetConfig.builder()
+                .setProperty(RemoteDeliveryConfiguration.DELIVERY_THREADS, "1")
+                .setProperty(RemoteDeliveryConfiguration.HELO_NAME, HELLO_NAME)
+                .build(),
+            mock(DomainList.class));
+        Bouncer testee = new Bouncer(configuration, mailetContext);
+
+        Mail mail = FakeMail.builder().state(Mail.DEFAULT)
+            .sender(MailAddressFixture.ANY_AT_JAMES)
+            .build();
+        String exceptionMessage = "Can not connect";
+        testee.bounce(mail, new MessagingException("Exception message", new 
MessagingException(exceptionMessage)));
+
+        FakeMailContext.BouncedMail expected = new 
FakeMailContext.BouncedMail(FakeMailContext.fromMail(mail),
+            "Hi. This is the James mail server at " + HELLO_NAME + ".\n" +
+                "I'm afraid I wasn't able to deliver your message to the 
following addresses.\n" +
+                "This is a permanent error; I've given up. Sorry it didn't 
work out. Below\n" +
+                "I include the list of recipients and the reason why I was 
unable to deliver\n" +
+                "your message.\n" +
+                "\n" +
+                exceptionMessage + "\n\n",
+            Optional.empty());
+        assertThat(mailetContext.getSentMails()).isEmpty();
+        assertThat(mailetContext.getBouncedMails()).containsOnly(expected);
+    }
+
+    @Test
+    public void bounceShouldNotBounceWithNoSenderByDefault() throws Exception {
+        RemoteDeliveryConfiguration configuration = new 
RemoteDeliveryConfiguration(
+            FakeMailetConfig.builder()
+                .setProperty(RemoteDeliveryConfiguration.DELIVERY_THREADS, "1")
+                .setProperty(RemoteDeliveryConfiguration.HELO_NAME, HELLO_NAME)
+                .build(),
+            mock(DomainList.class));
+        Bouncer testee = new Bouncer(configuration, mailetContext);
+
+        Mail mail = FakeMail.builder().state(Mail.DEFAULT)
+            .build();
+        String exceptionMessage = "Can not connect";
+        testee.bounce(mail, new MessagingException("Exception message", new 
ConnectException(exceptionMessage)));
+
+        assertThat(mailetContext.getSentMails()).isEmpty();
+        assertThat(mailetContext.getBouncedMails()).isEmpty();
+    }
+
+    @Test
+    public void bounceShouldSupportExceptionWithoutMessagesByDefault() throws 
Exception {
+        RemoteDeliveryConfiguration configuration = new 
RemoteDeliveryConfiguration(
+            FakeMailetConfig.builder()
+                .setProperty(RemoteDeliveryConfiguration.DELIVERY_THREADS, "1")
+                .setProperty(RemoteDeliveryConfiguration.HELO_NAME, HELLO_NAME)
+                .build(),
+            mock(DomainList.class));
+        Bouncer testee = new Bouncer(configuration, mailetContext);
+
+        Mail mail = FakeMail.builder().state(Mail.DEFAULT)
+            .sender(MailAddressFixture.ANY_AT_JAMES)
+            .build();
+        testee.bounce(mail, new Exception("Exception message"));
+
+        FakeMailContext.BouncedMail expected = new 
FakeMailContext.BouncedMail(FakeMailContext.fromMail(mail),
+            "Hi. This is the James mail server at " + HELLO_NAME + ".\n" +
+                "I'm afraid I wasn't able to deliver your message to the 
following addresses.\n" +
+                "This is a permanent error; I've given up. Sorry it didn't 
work out. Below\n" +
+                "I include the list of recipients and the reason why I was 
unable to deliver\n" +
+                "your message.\n\n\n",
+            Optional.empty());
+        assertThat(mailetContext.getSentMails()).isEmpty();
+        assertThat(mailetContext.getBouncedMails()).containsOnly(expected);
+    }
+
+    @Test
+    public void 
bounceShouldNotSupportMessagingExceptionWithoutMessagesByDefault() throws 
Exception {
+        RemoteDeliveryConfiguration configuration = new 
RemoteDeliveryConfiguration(
+            FakeMailetConfig.builder()
+                .setProperty(RemoteDeliveryConfiguration.DELIVERY_THREADS, "1")
+                .setProperty(RemoteDeliveryConfiguration.HELO_NAME, HELLO_NAME)
+                .build(),
+            mock(DomainList.class));
+        Bouncer testee = new Bouncer(configuration, mailetContext);
+
+        Mail mail = FakeMail.builder().state(Mail.DEFAULT)
+            .sender(MailAddressFixture.ANY_AT_JAMES)
+            .build();
+        testee.bounce(mail, new MessagingException());
+
+        FakeMailContext.BouncedMail expected = new 
FakeMailContext.BouncedMail(FakeMailContext.fromMail(mail),
+            "Hi. This is the James mail server at " + HELLO_NAME + ".\n" +
+                "I'm afraid I wasn't able to deliver your message to the 
following addresses.\n" +
+                "This is a permanent error; I've given up. Sorry it didn't 
work out. Below\n" +
+                "I include the list of recipients and the reason why I was 
unable to deliver\n" +
+                "your message.\n\nnull\n\n",
+            Optional.empty());
+        assertThat(mailetContext.getSentMails()).isEmpty();
+        assertThat(mailetContext.getBouncedMails()).containsOnly(expected);
+    }
+
+    @Test
+    public void bounceShouldWorkWhenProcessorSpecified() throws Exception {
+        RemoteDeliveryConfiguration configuration = new 
RemoteDeliveryConfiguration(
+            FakeMailetConfig.builder()
+                .setProperty(RemoteDeliveryConfiguration.DELIVERY_THREADS, "1")
+                .setProperty(RemoteDeliveryConfiguration.HELO_NAME, HELLO_NAME)
+                .setProperty(RemoteDeliveryConfiguration.BOUNCE_PROCESSOR, 
BOUNCE_PROCESSOR)
+                .build(),
+            mock(DomainList.class));
+        Bouncer testee = new Bouncer(configuration, mailetContext);
+
+        Mail mail = FakeMail.builder().state(Mail.DEFAULT)
+            .sender(MailAddressFixture.ANY_AT_JAMES)
+            .build();
+        String errorMessage = "message";
+        testee.bounce(mail, new MessagingException(errorMessage));
+
+        FakeMailContext.SentMail expected = FakeMailContext.sentMailBuilder()
+            .sender(MailAddressFixture.ANY_AT_JAMES)
+            .attribute(DELIVERY_ERROR, errorMessage)
+            .state(BOUNCE_PROCESSOR)
+            .fromMailet()
+            .build();
+        assertThat(mailetContext.getSentMails()).containsOnly(expected);
+        assertThat(mailetContext.getBouncedMails()).isEmpty();
+    }
+
+    @Test
+    public void bounceShouldNotBounceWhenNoSenderWhenProcessorSpecified() 
throws Exception {
+        RemoteDeliveryConfiguration configuration = new 
RemoteDeliveryConfiguration(
+            FakeMailetConfig.builder()
+                .setProperty(RemoteDeliveryConfiguration.DELIVERY_THREADS, "1")
+                .setProperty(RemoteDeliveryConfiguration.HELO_NAME, HELLO_NAME)
+                .setProperty(RemoteDeliveryConfiguration.BOUNCE_PROCESSOR, 
BOUNCE_PROCESSOR)
+                .build(),
+            mock(DomainList.class));
+        Bouncer testee = new Bouncer(configuration, mailetContext);
+
+        Mail mail = FakeMail.builder().state(Mail.DEFAULT)
+            .build();
+        testee.bounce(mail, new MessagingException("message"));
+
+        assertThat(mailetContext.getSentMails()).isEmpty();
+        assertThat(mailetContext.getBouncedMails()).isEmpty();
+    }
+
+    @Test
+    public void bounceShouldDisplayAddressByDefault() throws Exception {
+        RemoteDeliveryConfiguration configuration = new 
RemoteDeliveryConfiguration(
+            FakeMailetConfig.builder()
+                .setProperty(RemoteDeliveryConfiguration.DELIVERY_THREADS, "1")
+                .setProperty(RemoteDeliveryConfiguration.HELO_NAME, HELLO_NAME)
+                .build(),
+            mock(DomainList.class));
+        Bouncer testee = new Bouncer(configuration, mailetContext);
+
+        Mail mail = FakeMail.builder().state(Mail.DEFAULT)
+            .sender(MailAddressFixture.ANY_AT_JAMES)
+            .recipient(MailAddressFixture.ANY_AT_JAMES2)
+            .build();
+        testee.bounce(mail, new Exception("Exception message"));
+
+        FakeMailContext.BouncedMail expected = new 
FakeMailContext.BouncedMail(FakeMailContext.fromMail(mail),
+            "Hi. This is the James mail server at " + HELLO_NAME + ".\n" +
+                "I'm afraid I wasn't able to deliver your message to the 
following addresses.\n" +
+                "This is a permanent error; I've given up. Sorry it didn't 
work out. Below\n" +
+                "I include the list of recipients and the reason why I was 
unable to deliver\n" +
+                "your message.\n\n" +
+                MailAddressFixture.ANY_AT_JAMES2.asString() + "\n\n",
+            Optional.empty());
+        assertThat(mailetContext.getSentMails()).isEmpty();
+        assertThat(mailetContext.getBouncedMails()).containsOnly(expected);
+    }
+
+    @Test
+    public void bounceShouldDisplayAddressesByDefault() throws Exception {
+        RemoteDeliveryConfiguration configuration = new 
RemoteDeliveryConfiguration(
+            FakeMailetConfig.builder()
+                .setProperty(RemoteDeliveryConfiguration.DELIVERY_THREADS, "1")
+                .setProperty(RemoteDeliveryConfiguration.HELO_NAME, HELLO_NAME)
+                .build(),
+            mock(DomainList.class));
+        Bouncer testee = new Bouncer(configuration, mailetContext);
+
+        Mail mail = FakeMail.builder().state(Mail.DEFAULT)
+            .sender(MailAddressFixture.ANY_AT_JAMES)
+            .recipients(MailAddressFixture.ANY_AT_JAMES2, 
MailAddressFixture.OTHER_AT_JAMES2)
+            .build();
+        testee.bounce(mail, new Exception("Exception message"));
+
+        FakeMailContext.BouncedMail expected = new 
FakeMailContext.BouncedMail(FakeMailContext.fromMail(mail),
+            "Hi. This is the James mail server at " + HELLO_NAME + ".\n" +
+                "I'm afraid I wasn't able to deliver your message to the 
following addresses.\n" +
+                "This is a permanent error; I've given up. Sorry it didn't 
work out. Below\n" +
+                "I include the list of recipients and the reason why I was 
unable to deliver\n" +
+                "your message.\n\n" +
+                MailAddressFixture.ANY_AT_JAMES2.asString() + "\n" +
+                MailAddressFixture.OTHER_AT_JAMES2.asString() + "\n\n",
+            Optional.empty());
+        assertThat(mailetContext.getSentMails()).isEmpty();
+        assertThat(mailetContext.getBouncedMails()).containsOnly(expected);
+    }
+
+    @Test
+    public void bounceShouldWorkWhenProcessorSpecifiedAndNoExceptionMessage() 
throws Exception {
+        RemoteDeliveryConfiguration configuration = new 
RemoteDeliveryConfiguration(
+            FakeMailetConfig.builder()
+                .setProperty(RemoteDeliveryConfiguration.DELIVERY_THREADS, "1")
+                .setProperty(RemoteDeliveryConfiguration.HELO_NAME, HELLO_NAME)
+                .setProperty(RemoteDeliveryConfiguration.BOUNCE_PROCESSOR, 
BOUNCE_PROCESSOR)
+                .build(),
+            mock(DomainList.class));
+        Bouncer testee = new Bouncer(configuration, mailetContext);
+
+        Mail mail = FakeMail.builder().state(Mail.DEFAULT)
+            .sender(MailAddressFixture.ANY_AT_JAMES)
+            .build();
+        testee.bounce(mail, new MessagingException());
+
+        FakeMailContext.SentMail expected = FakeMailContext.sentMailBuilder()
+            .sender(MailAddressFixture.ANY_AT_JAMES)
+            .attribute(DELIVERY_ERROR, "null")
+            .state(BOUNCE_PROCESSOR)
+            .fromMailet()
+            .build();
+        assertThat(mailetContext.getSentMails()).containsOnly(expected);
+        assertThat(mailetContext.getBouncedMails()).isEmpty();
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/52c18ef6/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/DelayTest.java
----------------------------------------------------------------------
diff --git 
a/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/DelayTest.java
 
b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/DelayTest.java
new file mode 100644
index 0000000..3d55634
--- /dev/null
+++ 
b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/DelayTest.java
@@ -0,0 +1,124 @@
+/****************************************************************
+ * 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.james.transport.mailets.remote.delivery;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import javax.mail.MessagingException;
+
+import org.apache.james.transport.mailets.remote.delivery.Delay;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class DelayTest {
+
+    @Rule
+    public ExpectedException expectedException = ExpectedException.none();
+
+    @Test
+    public void defaultConstructorShouldConstructDefaultDelay() {
+        assertThat(new Delay())
+            .isEqualTo(new Delay(Delay.DEFAULT_ATTEMPTS, 
Delay.DEFAULT_DELAY_TIME));
+    }
+
+    @Test
+    public void stringConstructorShouldWorkForNumbers() throws Exception {
+        assertThat(Delay.from("36")).isEqualTo(new 
Delay(Delay.DEFAULT_ATTEMPTS, 36));
+    }
+
+    @Test
+    public void stringConstructorShouldWorkForZero() throws Exception {
+        assertThat(Delay.from("0")).isEqualTo(new 
Delay(Delay.DEFAULT_ATTEMPTS, 0));
+    }
+
+    @Test
+    public void stringConstructorShouldThrowOnNegativeNumbers() throws 
Exception {
+        expectedException.expect(NumberFormatException.class);
+        assertThat(Delay.from("-1s")).isEqualTo(new 
Delay(Delay.DEFAULT_ATTEMPTS, 0));
+    }
+
+    @Test
+    public void stringConstructorShouldWorkForNumberAndSecond() throws 
Exception {
+        assertThat(Delay.from("1s")).isEqualTo(new 
Delay(Delay.DEFAULT_ATTEMPTS, 1000));
+    }
+
+    @Test
+    public void stringConstructorShouldWorkForNumberAndAttempts() throws 
Exception {
+        assertThat(Delay.from("2*36")).isEqualTo(new Delay(2, 36));
+    }
+
+    @Test
+    public void stringConstructorShouldWorkForNumberAndZeroAttempts() throws 
Exception {
+        assertThat(Delay.from("0*36")).isEqualTo(new Delay(0, 36));
+    }
+
+    @Test
+    public void stringConstructorShouldThrowOnNegativeAttempts() throws 
Exception {
+        expectedException.expect(MessagingException.class);
+
+        Delay.from("-1*36");
+    }
+
+    @Test
+    public void stringConstructorShouldThrowWhenAttemptsOmitted() throws 
Exception {
+        expectedException.expect(NumberFormatException.class);
+
+        Delay.from("*36");
+    }
+
+    @Test
+    public void stringConstructorShouldThrowWhenDelayOmitted() throws 
Exception {
+        expectedException.expect(NumberFormatException.class);
+
+        Delay.from("2*");
+    }
+
+    @Test
+    public void stringConstructorShouldWorkForNumberAttemptsAndUnit() throws 
Exception {
+        assertThat(Delay.from("2*36s")).isEqualTo(new Delay(2, 36000));
+    }
+    
+    @Test
+    public void 
stringConstructorShouldWorkForNumberAttemptsAndUnitWithSpaces() throws 
Exception {
+        assertThat(Delay.from("2 * 36 s")).isEqualTo(new Delay(2, 36000));
+    }
+
+    @Test
+    public void stringConstructorShouldThrowOnInvalidInput() throws Exception {
+        expectedException.expect(NumberFormatException.class);
+
+        Delay.from("invalid");
+    }
+
+    @Test
+    public void stringConstructorShouldThrowOnInvalidUnit() throws Exception {
+        expectedException.expect(NumberFormatException.class);
+
+        Delay.from("36invalid");
+    }
+
+    @Test
+    public void stringConstructorShouldThrowOnEmptyString() throws Exception {
+        expectedException.expect(NumberFormatException.class);
+
+        Delay.from("");
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/52c18ef6/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/DelaysAndMaxRetryTest.java
----------------------------------------------------------------------
diff --git 
a/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/DelaysAndMaxRetryTest.java
 
b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/DelaysAndMaxRetryTest.java
new file mode 100644
index 0000000..7fb1895
--- /dev/null
+++ 
b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/DelaysAndMaxRetryTest.java
@@ -0,0 +1,145 @@
+/****************************************************************
+ * 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.james.transport.mailets.remote.delivery;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import javax.mail.MessagingException;
+
+import org.apache.james.transport.mailets.remote.delivery.Delay;
+import org.apache.james.transport.mailets.remote.delivery.DelaysAndMaxRetry;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import com.google.common.collect.ImmutableList;
+
+public class DelaysAndMaxRetryTest {
+
+    @Rule
+    public ExpectedException expectedException = ExpectedException.none();
+
+    @Test
+    public void fromShouldParseSingleDelay() throws Exception {
+        DelaysAndMaxRetry actual = DelaysAndMaxRetry.from(1, "1s");
+
+        DelaysAndMaxRetry expected = new DelaysAndMaxRetry(1, 
ImmutableList.of(new Delay(1, 1000)));
+
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void fromShouldParseTwoDelays() throws Exception {
+        DelaysAndMaxRetry actual = DelaysAndMaxRetry.from(2, "1s,2s");
+
+        DelaysAndMaxRetry expected = new DelaysAndMaxRetry(2, 
ImmutableList.of(new Delay(1, 1000), new Delay(1, 2000)));
+
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void fromShouldAdaptMaxRetriesWhenUnderAttempts() throws Exception {
+        DelaysAndMaxRetry actual = DelaysAndMaxRetry.from(1, "1s,2*2s");
+
+        DelaysAndMaxRetry expected = new DelaysAndMaxRetry(3, 
ImmutableList.of(new Delay(1, 1000), new Delay(2, 2000)));
+
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void fromShouldAdaptDelaysWhenUnderMaxRetries() throws Exception {
+        DelaysAndMaxRetry actual = DelaysAndMaxRetry.from(4, "1s,2*2s");
+
+        DelaysAndMaxRetry expected = new DelaysAndMaxRetry(4, 
ImmutableList.of(new Delay(1, 1000), new Delay(3, 2000)));
+
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void fromShouldHandleNullValuesForDelayAsString() throws Exception {
+        DelaysAndMaxRetry actual = DelaysAndMaxRetry.from(1, null);
+
+        DelaysAndMaxRetry expected = new DelaysAndMaxRetry(1, 
ImmutableList.of(new Delay(Delay.DEFAULT_ATTEMPTS, Delay.DEFAULT_DELAY_TIME)));
+
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void fromShouldIgnoreEmptyDelay() throws Exception {
+        DelaysAndMaxRetry actual = DelaysAndMaxRetry.from(1, "1s,,2s");
+
+        DelaysAndMaxRetry expected = new DelaysAndMaxRetry(2, 
ImmutableList.of(new Delay(1, 1000), new Delay(1, 2000)));
+
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void fromShouldHandleParsingFailures() throws Exception {
+        DelaysAndMaxRetry actual = DelaysAndMaxRetry.from(3, "1s,invalid,2s");
+
+        DelaysAndMaxRetry expected = new DelaysAndMaxRetry(3, 
ImmutableList.of(new Delay(3, 1000)));
+
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void fromShouldHandleEmptyStringWithZeroMaxRetries() throws 
Exception {
+        DelaysAndMaxRetry actual = DelaysAndMaxRetry.from(0, "");
+
+        DelaysAndMaxRetry expected = new DelaysAndMaxRetry(0, 
ImmutableList.<Delay>of());
+
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void fromShouldThrowOnEmptyStringWithNonZeroMaxRetry() throws 
Exception {
+        expectedException.expect(MessagingException.class);
+
+        DelaysAndMaxRetry.from(2, "");
+    }
+
+    @Test
+    public void getExpandedDelaysShouldReturnEmptyWhenNoDelay() throws 
Exception {
+        DelaysAndMaxRetry testee = DelaysAndMaxRetry.from(0, "");
+
+        assertThat(testee.getExpandedDelays()).isEmpty();
+    }
+
+    @Test
+    public void getExpandedDelaysShouldExpandSingleDelays() throws Exception {
+        DelaysAndMaxRetry testee = DelaysAndMaxRetry.from(3, "1*1S,1*2S,1*5S");
+
+        assertThat(testee.getExpandedDelays()).containsExactly(1000L, 2000L, 
5000L);
+    }
+
+    @Test
+    public void getExpandedDelaysShouldExpandMultipleDelays() throws Exception 
{
+        DelaysAndMaxRetry testee = DelaysAndMaxRetry.from(3, "1*1S,2*2S,2*5S");
+
+        assertThat(testee.getExpandedDelays()).containsExactly(1000L, 2000L, 
2000L, 5000L, 5000L);
+    }
+    
+    @Test
+    public void getExpandedDelaysShouldExpandMultipleDelaysWithSpaces() throws 
Exception {
+        DelaysAndMaxRetry testee = DelaysAndMaxRetry.from(3, "1 * 1 S, 2 * 2 S 
, 2 * 5 S");
+
+        assertThat(testee.getExpandedDelays()).containsExactly(1000L, 2000L, 
2000L, 5000L, 5000L);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/52c18ef6/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/DeliveryRetryHelperTest.java
----------------------------------------------------------------------
diff --git 
a/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/DeliveryRetryHelperTest.java
 
b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/DeliveryRetryHelperTest.java
new file mode 100644
index 0000000..0348a74
--- /dev/null
+++ 
b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/DeliveryRetryHelperTest.java
@@ -0,0 +1,84 @@
+/****************************************************************
+ * 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.james.transport.mailets.remote.delivery;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import 
org.apache.james.transport.mailets.remote.delivery.DeliveryRetriesHelper;
+import org.apache.mailet.base.test.FakeMail;
+import org.junit.Test;
+
+public class DeliveryRetryHelperTest {
+
+    @Test
+    public void retrieveRetriesShouldBeZeroByDefault() throws Exception {
+        
assertThat(DeliveryRetriesHelper.retrieveRetries(FakeMail.defaultFakeMail()))
+            .isEqualTo(0);
+    }
+
+    @Test
+    public void retrieveRetriesShouldBeZeroAfterInit() throws Exception {
+        FakeMail mail = FakeMail.defaultFakeMail();
+
+        DeliveryRetriesHelper.initRetries(mail);
+
+        assertThat(DeliveryRetriesHelper.retrieveRetries(mail))
+            .isEqualTo(0);
+    }
+
+    @Test
+    public void retrieveRetriesShouldBeOneAfterIncrement() throws Exception {
+        FakeMail mail = FakeMail.defaultFakeMail();
+
+        DeliveryRetriesHelper.initRetries(mail);
+        DeliveryRetriesHelper.incrementRetries(mail);
+
+        assertThat(DeliveryRetriesHelper.retrieveRetries(mail))
+            .isEqualTo(1);
+    }
+
+    @Test
+    public void incrementRetriesShouldWorkOnNonInitializedMails() throws 
Exception {
+        FakeMail mail = FakeMail.defaultFakeMail();
+
+        DeliveryRetriesHelper.incrementRetries(mail);
+
+        assertThat(DeliveryRetriesHelper.retrieveRetries(mail))
+            .isEqualTo(1);
+    }
+
+    @Test
+    public void retrieveRetriesShouldBeZeroOnInvalidValue() throws Exception {
+        FakeMail mail = 
FakeMail.builder().attribute(DeliveryRetriesHelper.DELIVERY_RETRY_COUNT, 
"invalid").build();
+
+        assertThat(DeliveryRetriesHelper.retrieveRetries(mail))
+            .isEqualTo(0);
+    }
+
+    @Test
+    public void incrementRetriesShouldWorkOnInvalidMails() throws Exception {
+        FakeMail mail = 
FakeMail.builder().attribute(DeliveryRetriesHelper.DELIVERY_RETRY_COUNT, 
"invalid").build();
+
+        DeliveryRetriesHelper.incrementRetries(mail);
+
+        assertThat(DeliveryRetriesHelper.retrieveRetries(mail))
+            .isEqualTo(1);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/52c18ef6/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/DeliveryRunnableTest.java
----------------------------------------------------------------------
diff --git 
a/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/DeliveryRunnableTest.java
 
b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/DeliveryRunnableTest.java
new file mode 100644
index 0000000..8cfd16a
--- /dev/null
+++ 
b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/DeliveryRunnableTest.java
@@ -0,0 +1,251 @@
+/****************************************************************
+ * 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.james.transport.mailets.remote.delivery;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.metrics.api.Metric;
+import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.metrics.api.NoopMetricFactory;
+import org.apache.james.queue.api.MailQueue;
+import org.apache.james.transport.mailets.remote.delivery.Bouncer;
+import 
org.apache.james.transport.mailets.remote.delivery.DeliveryRetriesHelper;
+import org.apache.james.transport.mailets.remote.delivery.DeliveryRunnable;
+import org.apache.james.transport.mailets.remote.delivery.ExecutionResult;
+import org.apache.james.transport.mailets.remote.delivery.MailDelivrer;
+import 
org.apache.james.transport.mailets.remote.delivery.RemoteDeliveryConfiguration;
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.test.FakeMail;
+import org.apache.mailet.base.test.FakeMailetConfig;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class DeliveryRunnableTest {
+
+    public static final Date FIXED_DATE = new Date(1159599194961L);
+    public static final Supplier<Date> FIXED_DATE_SUPPLIER = () -> FIXED_DATE;
+
+    @Rule
+    public ExpectedException expectedException = ExpectedException.none();
+
+    private DeliveryRunnable testee;
+    private Metric outgoingMailsMetric;
+    private Bouncer bouncer;
+    private MailDelivrer mailDelivrer;
+    private MailQueue mailQueue;
+
+    @Before
+    public void setUp() {
+        FakeMailetConfig mailetConfig = FakeMailetConfig.builder()
+            .setProperty(RemoteDeliveryConfiguration.DELIVERY_THREADS, "1")
+            .setProperty(RemoteDeliveryConfiguration.DEBUG, "true")
+            .setProperty(RemoteDeliveryConfiguration.DELAY_TIME, 
"1000,2000,3000,4000,5000")
+            .build();
+
+        RemoteDeliveryConfiguration configuration = new 
RemoteDeliveryConfiguration(mailetConfig, mock(DomainList.class));
+        outgoingMailsMetric = mock(Metric.class);
+        MetricFactory mockMetricFactory = mock(MetricFactory.class);
+        
when(mockMetricFactory.generate(anyString())).thenReturn(outgoingMailsMetric);
+        when(mockMetricFactory.timer(anyString())).thenReturn(new 
NoopMetricFactory.NoopTimeMetric());
+        bouncer = mock(Bouncer.class);
+        mailDelivrer = mock(MailDelivrer.class);
+        mailQueue = mock(MailQueue.class);
+        testee = new DeliveryRunnable(mailQueue, configuration, 
mockMetricFactory, bouncer, mailDelivrer, DeliveryRunnable.DEFAULT_NOT_STARTED, 
FIXED_DATE_SUPPLIER);
+    }
+
+    @Test
+    public void deliverySuccessShouldIncrementMetric() throws Exception {
+        FakeMail fakeMail = FakeMail.defaultFakeMail();
+        
when(mailDelivrer.deliver(fakeMail)).thenReturn(ExecutionResult.success());
+
+        testee.attemptDelivery(fakeMail);
+
+        verify(outgoingMailsMetric).increment();
+        verifyNoMoreInteractions(outgoingMailsMetric);
+    }
+
+    @Test
+    public void deliveryPermanentFailureShouldBounceTheMail() throws Exception 
{
+        FakeMail fakeMail = FakeMail.defaultFakeMail();
+        Exception exception = new Exception();
+        
when(mailDelivrer.deliver(fakeMail)).thenReturn(ExecutionResult.permanentFailure(exception));
+
+        testee.attemptDelivery(fakeMail);
+
+        verify(bouncer).bounce(fakeMail, exception);
+        verifyNoMoreInteractions(bouncer);
+    }
+
+    @Test
+    public void deliveryPermanentFailureShouldNotIncrementDeliveryMetric() 
throws Exception {
+        FakeMail fakeMail = FakeMail.defaultFakeMail();
+        Exception exception = new Exception();
+        
when(mailDelivrer.deliver(fakeMail)).thenReturn(ExecutionResult.permanentFailure(exception));
+
+        testee.attemptDelivery(fakeMail);
+
+        verifyNoMoreInteractions(outgoingMailsMetric);
+    }
+
+    @Test
+    public void deliveryTemporaryFailureShouldNotIncrementDeliveryMetric() 
throws Exception {
+        FakeMail fakeMail = FakeMail.builder().state(Mail.DEFAULT).build();
+        Exception exception = new Exception();
+        
when(mailDelivrer.deliver(fakeMail)).thenReturn(ExecutionResult.temporaryFailure(exception));
+
+        testee.attemptDelivery(fakeMail);
+
+        verifyNoMoreInteractions(outgoingMailsMetric);
+    }
+
+    @Test
+    public void deliveryTemporaryFailureShouldFailOnMailsWithoutState() throws 
Exception {
+        FakeMail fakeMail = FakeMail.defaultFakeMail();
+        Exception exception = new Exception();
+        
when(mailDelivrer.deliver(fakeMail)).thenReturn(ExecutionResult.temporaryFailure(exception));
+
+        expectedException.expect(NullPointerException.class);
+
+        testee.attemptDelivery(fakeMail);
+    }
+
+    @Test
+    public void deliveryTemporaryFailureShouldRetryDelivery() throws Exception 
{
+        FakeMail fakeMail = FakeMail.builder().state(Mail.DEFAULT).build();
+        Exception exception = new Exception();
+        
when(mailDelivrer.deliver(fakeMail)).thenReturn(ExecutionResult.temporaryFailure(exception));
+
+        testee.attemptDelivery(fakeMail);
+
+        verify(mailQueue).enQueue(FakeMail.builder()
+                .attribute(DeliveryRetriesHelper.DELIVERY_RETRY_COUNT, 1)
+                .state(Mail.ERROR)
+                .lastUpdated(FIXED_DATE)
+                .build(),
+            1000,
+            TimeUnit.MILLISECONDS);
+        verifyNoMoreInteractions(mailQueue);
+    }
+
+    @Test
+    public void deliveryTemporaryFailureShouldRetryDeliveryWithRightDelay() 
throws Exception {
+        FakeMail fakeMail = FakeMail.builder()
+            .state(Mail.ERROR)
+            .attribute(DeliveryRetriesHelper.DELIVERY_RETRY_COUNT, 2)
+            .build();
+        Exception exception = new Exception();
+        
when(mailDelivrer.deliver(fakeMail)).thenReturn(ExecutionResult.temporaryFailure(exception));
+
+        testee.attemptDelivery(fakeMail);
+
+        verify(mailQueue).enQueue(FakeMail.builder()
+                .attribute(DeliveryRetriesHelper.DELIVERY_RETRY_COUNT, 3)
+                .state(Mail.ERROR)
+                .lastUpdated(FIXED_DATE)
+                .build(),
+            3000,
+            TimeUnit.MILLISECONDS);
+        verifyNoMoreInteractions(mailQueue);
+    }
+
+    @Test
+    public void 
deliveryTemporaryFailureShouldRetryDeliveryOnMaximumRetryNumber() throws 
Exception {
+        FakeMail fakeMail = FakeMail.builder()
+            .state(Mail.ERROR)
+            .attribute(DeliveryRetriesHelper.DELIVERY_RETRY_COUNT, 4)
+            .build();
+        Exception exception = new Exception();
+        
when(mailDelivrer.deliver(fakeMail)).thenReturn(ExecutionResult.temporaryFailure(exception));
+
+        testee.attemptDelivery(fakeMail);
+
+        verify(mailQueue).enQueue(FakeMail.builder()
+                .attribute(DeliveryRetriesHelper.DELIVERY_RETRY_COUNT, 5)
+                .state(Mail.ERROR)
+                .lastUpdated(FIXED_DATE)
+                .build(),
+            5000,
+            TimeUnit.MILLISECONDS);
+        verifyNoMoreInteractions(mailQueue);
+    }
+
+    @Test
+    public void 
deliveryTemporaryFailureShouldNotRetryDeliveryOverMaximumRetryNumber() throws 
Exception {
+        FakeMail fakeMail = FakeMail.builder()
+            .state(Mail.ERROR)
+            .attribute(DeliveryRetriesHelper.DELIVERY_RETRY_COUNT, 5)
+            .build();
+        Exception exception = new Exception();
+        
when(mailDelivrer.deliver(fakeMail)).thenReturn(ExecutionResult.temporaryFailure(exception));
+
+        testee.attemptDelivery(fakeMail);
+
+        verifyNoMoreInteractions(mailQueue);
+    }
+
+    @Test
+    public void deliveryTemporaryFailureShouldBounceWhenRetryExceeded() throws 
Exception {
+        FakeMail fakeMail = FakeMail.builder()
+            .state(Mail.ERROR)
+            .attribute(DeliveryRetriesHelper.DELIVERY_RETRY_COUNT, 5)
+            .build();
+        Exception exception = new Exception("");
+        
when(mailDelivrer.deliver(fakeMail)).thenReturn(ExecutionResult.temporaryFailure(exception));
+
+        testee.attemptDelivery(fakeMail);
+
+        verify(bouncer).bounce(eq(fakeMail), any(Exception.class));
+        verifyNoMoreInteractions(bouncer);
+    }
+
+    @Test
+    public void 
deliveryTemporaryFailureShouldResetDeliveryCountOnNonErrorState() throws 
Exception {
+        FakeMail fakeMail = FakeMail.builder()
+            .state(Mail.DEFAULT)
+            .attribute(DeliveryRetriesHelper.DELIVERY_RETRY_COUNT, 5)
+            .build();
+        Exception exception = new Exception();
+        
when(mailDelivrer.deliver(fakeMail)).thenReturn(ExecutionResult.temporaryFailure(exception));
+
+        testee.attemptDelivery(fakeMail);
+
+        verify(mailQueue).enQueue(FakeMail.builder()
+                .attribute(DeliveryRetriesHelper.DELIVERY_RETRY_COUNT, 1)
+                .state(Mail.ERROR)
+                .lastUpdated(FIXED_DATE)
+                .build(),
+            1000,
+            TimeUnit.MILLISECONDS);
+        verifyNoMoreInteractions(mailQueue);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/52c18ef6/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/HeloNameProviderTest.java
----------------------------------------------------------------------
diff --git 
a/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/HeloNameProviderTest.java
 
b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/HeloNameProviderTest.java
new file mode 100644
index 0000000..1ac0d55
--- /dev/null
+++ 
b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/HeloNameProviderTest.java
@@ -0,0 +1,80 @@
+/****************************************************************
+ * 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.james.transport.mailets.remote.delivery;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.domainlist.api.DomainListException;
+import org.apache.james.transport.mailets.remote.delivery.HeloNameProvider;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class HeloNameProviderTest {
+
+    public static final String DOMAIN = "domain";
+
+    @Rule
+    public ExpectedException expectedException = ExpectedException.none();
+
+    private DomainList domainList;
+
+    @Before
+    public void setUp() {
+        domainList = mock(DomainList.class);
+    }
+
+    @Test
+    public void getHeloNameShouldReturnNonNullProvidedHeloName() {
+        HeloNameProvider heloNameProvider = new HeloNameProvider(DOMAIN, 
domainList);
+
+        assertThat(heloNameProvider.getHeloName()).isEqualTo(DOMAIN);
+    }
+
+    @Test
+    public void getHeloNameShouldReturnDomainListDefaultDomainOnNullHeloName() 
throws DomainListException {
+        when(domainList.getDefaultDomain()).thenReturn(DOMAIN);
+        HeloNameProvider heloNameProvider = new HeloNameProvider(null, 
domainList);
+
+        assertThat(heloNameProvider.getHeloName()).isEqualTo(DOMAIN);
+    }
+
+    @Test
+    public void getHeloNameShouldReturnLocalhostOnDomainListException() throws 
DomainListException {
+        when(domainList.getDefaultDomain()).thenThrow(new 
DomainListException("any message"));
+        HeloNameProvider heloNameProvider = new HeloNameProvider(null, 
domainList);
+
+        
assertThat(heloNameProvider.getHeloName()).isEqualTo(HeloNameProvider.LOCALHOST);
+    }
+
+    @Test
+    public void getHeloNameShouldPropagateRuntimeExceptions() throws 
DomainListException {
+        when(domainList.getDefaultDomain()).thenThrow(new RuntimeException());
+        HeloNameProvider heloNameProvider = new HeloNameProvider(null, 
domainList);
+
+        expectedException.expect(RuntimeException.class);
+        heloNameProvider.getHeloName();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/52c18ef6/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/InternetAddressConverterTest.java
----------------------------------------------------------------------
diff --git 
a/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/InternetAddressConverterTest.java
 
b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/InternetAddressConverterTest.java
new file mode 100644
index 0000000..ef1308c
--- /dev/null
+++ 
b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/InternetAddressConverterTest.java
@@ -0,0 +1,63 @@
+/****************************************************************
+ * 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.james.transport.mailets.remote.delivery;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import javax.mail.internet.InternetAddress;
+
+import org.apache.james.core.MailAddress;
+import 
org.apache.james.transport.mailets.remote.delivery.InternetAddressConverter;
+import org.apache.mailet.base.MailAddressFixture;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import com.google.common.collect.ImmutableList;
+
+public class InternetAddressConverterTest {
+
+    @Rule
+    public ExpectedException expectedException = ExpectedException.none();
+
+    @Test
+    public void convertShouldWorkWithEmptyAddressList() {
+        
assertThat(InternetAddressConverter.convert(ImmutableList.<MailAddress>of())).isEmpty();
+    }
+
+    @Test
+    public void convertShouldThrowOnNullAddress() {
+        expectedException.expect(NullPointerException.class);
+
+        InternetAddressConverter.convert(null);
+    }
+
+    @Test
+    public void convertShouldWorkWithOneAddress() throws Exception {
+        
assertThat(InternetAddressConverter.convert(ImmutableList.of(MailAddressFixture.ANY_AT_JAMES)))
+            .containsOnly(new 
InternetAddress(MailAddressFixture.ANY_AT_JAMES.asString()));
+    }
+
+    @Test
+    public void convertShouldWorkWithTwoAddress() throws Exception {
+        
assertThat(InternetAddressConverter.convert(ImmutableList.of(MailAddressFixture.ANY_AT_JAMES,
 MailAddressFixture.OTHER_AT_JAMES)))
+            .containsOnly(new 
InternetAddress(MailAddressFixture.ANY_AT_JAMES.asString()), new 
InternetAddress(MailAddressFixture.OTHER_AT_JAMES.asString()));
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to