org.apache.james.queue.api.MailQueueContract.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.james.queue.api.MailQueueContract.java

Source

/****************************************************************
 * 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.queue.api;

import static org.apache.james.queue.api.Mails.createMimeMessage;
import static org.apache.james.queue.api.Mails.defaultMail;
import static org.apache.james.util.MimeMessageUtil.asString;
import static org.apache.mailet.base.MailAddressFixture.RECIPIENT1;
import static org.apache.mailet.base.MailAddressFixture.RECIPIENT2;
import static org.apache.mailet.base.MailAddressFixture.SENDER;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.io.Serializable;
import java.time.Duration;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import javax.mail.internet.MimeMessage;

import org.apache.james.core.MailAddress;
import org.apache.james.core.MaybeSender;
import org.apache.james.core.builder.MimeMessageBuilder;
import org.apache.james.junit.ExecutorExtension;
import org.apache.james.util.concurrency.ConcurrentTestRunner;
import org.apache.mailet.Mail;
import org.apache.mailet.PerRecipientHeaders;
import org.apache.mailet.base.test.FakeMail;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import com.github.fge.lambdas.Throwing;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;

@ExtendWith(ExecutorExtension.class)
public interface MailQueueContract {

    MailQueue getMailQueue();

    default void enQueue(Mail mail) throws MailQueue.MailQueueException {
        getMailQueue().enQueue(mail);
    }

    @Test
    default void queueShouldSupportBigMail() throws Exception {
        String name = "name1";
        // 12 MB of text
        String messageText = Strings.repeat("0123456789\r\n", 1024 * 1024);
        FakeMail mail = defaultMail().name(name)
                .mimeMessage(MimeMessageBuilder.mimeMessageBuilder().setText(messageText)).build();
        enQueue(mail);

        MailQueue.MailQueueItem mailQueueItem = getMailQueue().deQueue();
        assertThat(mailQueueItem.getMail().getName()).isEqualTo(name);
    }

    @Test
    default void queueShouldPreserveMailRecipients() throws Exception {
        enQueue(defaultMail().recipients(RECIPIENT1, RECIPIENT2).build());

        MailQueue.MailQueueItem mailQueueItem = getMailQueue().deQueue();
        assertThat(mailQueueItem.getMail().getRecipients()).containsOnly(RECIPIENT1, RECIPIENT2);
    }

    @Test
    default void queueShouldHandleSender() throws Exception {
        enQueue(FakeMail.builder().name("name").mimeMessage(createMimeMessage()).recipients(RECIPIENT1, RECIPIENT2)
                .sender(MailAddress.nullSender()).lastUpdated(new Date()).build());

        MailQueue.MailQueueItem mailQueueItem = getMailQueue().deQueue();
        assertThat(mailQueueItem.getMail().getMaybeSender()).isEqualTo(MaybeSender.nullSender());
    }

    @Test
    default void queueShouldHandleNoSender() throws Exception {
        enQueue(FakeMail.builder().name("name").mimeMessage(createMimeMessage()).recipients(RECIPIENT1, RECIPIENT2)
                .lastUpdated(new Date()).build());

        MailQueue.MailQueueItem mailQueueItem = getMailQueue().deQueue();
        assertThat(mailQueueItem.getMail().getMaybeSender()).isEqualTo(MaybeSender.nullSender());
    }

    @Test
    default void queueShouldPreserveMailSender() throws Exception {
        enQueue(defaultMail().sender(SENDER).build());

        MailQueue.MailQueueItem mailQueueItem = getMailQueue().deQueue();
        assertThat(mailQueueItem.getMail().getMaybeSender()).isEqualTo(MaybeSender.of(SENDER));
    }

    @Test
    default void queueShouldPreserveMimeMessage() throws Exception {
        MimeMessage originalMimeMessage = createMimeMessage();
        enQueue(defaultMail().mimeMessage(originalMimeMessage).build());

        MailQueue.MailQueueItem mailQueueItem = getMailQueue().deQueue();
        assertThat(asString(mailQueueItem.getMail().getMessage())).isEqualTo(asString(originalMimeMessage));
    }

    @Test
    default void queueShouldPreserveMailAttribute() throws Exception {
        String attributeName = "any";
        String attributeValue = "value";
        enQueue(defaultMail().attribute(attributeName, attributeValue).build());

        MailQueue.MailQueueItem mailQueueItem = getMailQueue().deQueue();
        assertThat(mailQueueItem.getMail().getAttribute(attributeName)).isEqualTo(attributeValue);
    }

    @Test
    default void queueShouldPreserveErrorMessage() throws Exception {
        String errorMessage = "ErrorMessage";
        enQueue(defaultMail().errorMessage(errorMessage).build());

        MailQueue.MailQueueItem mailQueueItem = getMailQueue().deQueue();
        assertThat(mailQueueItem.getMail().getErrorMessage()).isEqualTo(errorMessage);
    }

    @Test
    default void queueShouldPreserveState() throws Exception {
        String state = "state";
        enQueue(defaultMail().state(state).build());

        MailQueue.MailQueueItem mailQueueItem = getMailQueue().deQueue();
        assertThat(mailQueueItem.getMail().getState()).isEqualTo(state);
    }

    @Test
    default void queueShouldPreserveRemoteAddress() throws Exception {
        String remoteAddress = "remote";
        enQueue(defaultMail().remoteAddr(remoteAddress).build());

        MailQueue.MailQueueItem mailQueueItem = getMailQueue().deQueue();
        assertThat(mailQueueItem.getMail().getRemoteAddr()).isEqualTo(remoteAddress);
    }

    @Test
    default void queueShouldPreserveRemoteHost() throws Exception {
        String remoteHost = "remote";
        enQueue(defaultMail().remoteHost(remoteHost).build());

        MailQueue.MailQueueItem mailQueueItem = getMailQueue().deQueue();
        assertThat(mailQueueItem.getMail().getRemoteHost()).isEqualTo(remoteHost);
    }

    @Test
    default void queueShouldPreserveLastUpdated() throws Exception {
        Date lastUpdated = new Date();
        enQueue(defaultMail().lastUpdated(lastUpdated).build());

        MailQueue.MailQueueItem mailQueueItem = getMailQueue().deQueue();
        assertThat(mailQueueItem.getMail().getLastUpdated()).isEqualTo(lastUpdated);
    }

    @Test
    default void queueShouldPreserveName() throws Exception {
        String expectedName = "name";
        enQueue(defaultMail().name(expectedName).build());

        MailQueue.MailQueueItem mailQueueItem = getMailQueue().deQueue();
        assertThat(mailQueueItem.getMail().getName()).isEqualTo(expectedName);
    }

    @Test
    default void queueShouldPreservePerRecipientHeaders() throws Exception {
        PerRecipientHeaders.Header header = PerRecipientHeaders.Header.builder().name("any").value("any").build();
        enQueue(defaultMail().addHeaderForRecipient(header, RECIPIENT1).build());

        MailQueue.MailQueueItem mailQueueItem = getMailQueue().deQueue();
        assertThat(mailQueueItem.getMail().getPerRecipientSpecificHeaders().getHeadersForRecipient(RECIPIENT1))
                .containsOnly(header);
    }

    @Test
    default void queueShouldPreserveNonStringMailAttribute() throws Exception {
        String attributeName = "any";
        SerializableAttribute attributeValue = new SerializableAttribute("value");
        enQueue(defaultMail().attribute(attributeName, attributeValue).build());

        MailQueue.MailQueueItem mailQueueItem = getMailQueue().deQueue();
        assertThat(mailQueueItem.getMail().getAttribute(attributeName)).isInstanceOf(SerializableAttribute.class)
                .isEqualTo(attributeValue);
    }

    @Test
    default void dequeueShouldBeFifo() throws Exception {
        String firstExpectedName = "name1";
        enQueue(defaultMail().name(firstExpectedName).build());
        String secondExpectedName = "name2";
        enQueue(defaultMail().name(secondExpectedName).build());

        MailQueue.MailQueueItem mailQueueItem1 = getMailQueue().deQueue();
        mailQueueItem1.done(true);
        MailQueue.MailQueueItem mailQueueItem2 = getMailQueue().deQueue();
        mailQueueItem2.done(true);
        assertThat(mailQueueItem1.getMail().getName()).isEqualTo(firstExpectedName);
        assertThat(mailQueueItem2.getMail().getName()).isEqualTo(secondExpectedName);
    }

    @Test
    default void dequeueCanBeChainedBeforeAck() throws Exception {
        enQueue(defaultMail().name("name1").build());
        enQueue(defaultMail().name("name2").build());

        MailQueue.MailQueueItem mailQueueItem1 = getMailQueue().deQueue();
        MailQueue.MailQueueItem mailQueueItem2 = getMailQueue().deQueue();
        mailQueueItem1.done(true);
        mailQueueItem2.done(true);
        assertThat(mailQueueItem1.getMail().getName()).isEqualTo("name1");
        assertThat(mailQueueItem2.getMail().getName()).isEqualTo("name2");
    }

    @Test
    default void dequeueCouldBeInterleavingWithOutOfOrderAck() throws Exception {
        enQueue(defaultMail().name("name1").build());
        enQueue(defaultMail().name("name2").build());

        MailQueue.MailQueueItem mailQueueItem1 = getMailQueue().deQueue();
        MailQueue.MailQueueItem mailQueueItem2 = getMailQueue().deQueue();
        mailQueueItem2.done(true);
        mailQueueItem1.done(true);
        assertThat(mailQueueItem1.getMail().getName()).isEqualTo("name1");
        assertThat(mailQueueItem2.getMail().getName()).isEqualTo("name2");
    }

    @Test
    default void dequeueShouldAllowRetrieveFailItems() throws Exception {
        enQueue(defaultMail().name("name1").build());

        MailQueue.MailQueueItem mailQueueItem1 = getMailQueue().deQueue();
        mailQueueItem1.done(false);
        MailQueue.MailQueueItem mailQueueItem2 = getMailQueue().deQueue();
        mailQueueItem2.done(true);
        assertThat(mailQueueItem1.getMail().getName()).isEqualTo("name1");
        assertThat(mailQueueItem2.getMail().getName()).isEqualTo("name1");
    }

    @Test
    default void dequeueShouldNotReturnInProcessingEmails(ExecutorService executorService) throws Exception {
        enQueue(defaultMail().name("name").build());

        getMailQueue().deQueue();

        Future<?> future = executorService.submit(Throwing.runnable(() -> getMailQueue().deQueue()));
        assertThatThrownBy(() -> future.get(2, TimeUnit.SECONDS)).isInstanceOf(TimeoutException.class);
    }

    @Test
    default void deQueueShouldBlockWhenNoMail(ExecutorService executorService) {
        Future<?> future = executorService.submit(Throwing.runnable(() -> getMailQueue().deQueue()));

        assertThatThrownBy(() -> future.get(2, TimeUnit.SECONDS)).isInstanceOf(TimeoutException.class);
    }

    @Test
    default void deQueueShouldWaitForAMailToBeEnqueued(ExecutorService executorService) throws Exception {
        MailQueue testee = getMailQueue();

        Mail mail = defaultMail().name("name").build();
        Future<MailQueue.MailQueueItem> tryDequeue = executorService.submit(testee::deQueue);
        testee.enQueue(mail);

        assertThat(tryDequeue.get().getMail().getName()).isEqualTo("name");
    }

    @Test
    default void concurrentEnqueueDequeueShouldNotFail() throws Exception {
        MailQueue testee = getMailQueue();

        ConcurrentLinkedDeque<Mail> dequeuedMails = new ConcurrentLinkedDeque<>();

        int threadCount = 10;
        int operationCount = 10;
        int totalDequeuedMessages = 50;
        ConcurrentTestRunner.builder().operation((threadNumber, step) -> {
            if (step % 2 == 0) {
                testee.enQueue(defaultMail().name("name" + threadNumber + "-" + step).build());
            } else {
                MailQueue.MailQueueItem mailQueueItem = testee.deQueue();
                dequeuedMails.add(mailQueueItem.getMail());
                mailQueueItem.done(true);
            }
        }).threadCount(threadCount).operationCount(operationCount).runSuccessfullyWithin(Duration.ofMinutes(5));

        assertThat(dequeuedMails.stream().map(Mail::getName).distinct()).hasSize(totalDequeuedMessages);
    }

    @Test
    default void concurrentEnqueueDequeueWithAckNackShouldNotFail() throws Exception {
        MailQueue testee = getMailQueue();

        ConcurrentLinkedDeque<Mail> dequeuedMails = new ConcurrentLinkedDeque<>();

        int threadCount = 10;
        int operationCount = 15;
        int totalDequeuedMessages = 50;
        ConcurrentTestRunner.builder().operation((threadNumber, step) -> {
            if (step % 3 == 0) {
                testee.enQueue(defaultMail().name("name" + threadNumber + "-" + step).build());
            }
            if (step % 3 == 1) {
                MailQueue.MailQueueItem mailQueueItem = testee.deQueue();
                mailQueueItem.done(false);
            }
            if (step % 3 == 2) {
                MailQueue.MailQueueItem mailQueueItem = testee.deQueue();
                dequeuedMails.add(mailQueueItem.getMail());
                mailQueueItem.done(true);
            }
        }).threadCount(threadCount).operationCount(operationCount).runSuccessfullyWithin(Duration.ofMinutes(1));

        assertThat(dequeuedMails.stream().map(Mail::getName).distinct()).hasSize(totalDequeuedMessages);
    }

    class SerializableAttribute implements Serializable {
        private final String value;

        SerializableAttribute(String value) {
            this.value = value;
        }

        @Override
        public final boolean equals(Object o) {
            if (o instanceof SerializableAttribute) {
                SerializableAttribute that = (SerializableAttribute) o;

                return Objects.equals(this.value, that.value);
            }
            return false;
        }

        @Override
        public final int hashCode() {
            return Objects.hash(value);
        }

        @Override
        public String toString() {
            return MoreObjects.toStringHelper(this).add("value", value).toString();
        }
    }
}